/*
 *   This file is part of ed_pvr.
 *
 *   ed_pvr is free software: you can redistribute it and/or modify
 *   it under the terms of the GNU General Public License as published by
 *   the Free Software Foundation, either version 2 of the License, or
 *   (at your option) any later version.
 *
 *   ed_pvr is distributed in the hope that it will be useful,
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *   GNU General Public License for more details.
 *
 *   You should have received a copy of the GNU General Public License
 *   along with ed_pvr.  If not, see <http://www.gnu.org/licenses/>.
 */

#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <stdarg.h>
#include <inttypes.h>
#include <fcntl.h>
#include <unistd.h>
#include <memory.h>
#include <math.h>
#include <getopt.h>
#include <pthread.h>
#include <errno.h>
#include <signal.h>
#include <ctype.h>
#include <ncurses.h>
#include <sys/ioctl.h>
#include <curl/curl.h>
#include <libxml/parser.h>
#include <libxml/tree.h>
#include <openssl/aes.h>
#include <openssl/evp.h>

#include "device.h"
#include "utils.h"
#include "mpegts_io.h"
#include "tuner_mlb.h"
#include "hls.h"
#include "xmltv.h"
#include "protocol.h"

#define _FORCE_FILE_START_TIME					"/tmp/mlb_start_time"
#define _FORCE_FILE_AUTH_PROXY					"/tmp/mlb_aproxy"

//default icon location: http://mlb.com/images/homepage/team/y2011/logos/nym.png

#define MLB_DEFAULT_REAUTH_TIME					(60*5) // in seconds


#define USER_AGENT 								"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/535.11 (KHTML, like Gecko) Chrome/17.0.963.79 Safari/535.11"

#define MLB_DEFAULT_COOKIEFILE					"mlb_cookies.txt"
#define MLB_DEFAULT_CONF_FILE					"mlb.conf"
#define MLB_DEFAULT_TEAM_FILE					"mlb_chanmap.txt"
#define MLB_DEFAULT_SESKEY_FILE					"mlb_ses_key.txt"

#define MLB_CMARKER_IPID						"ipid"
#define MLB_CMARKER_FPRT						"fprt"
#define MLB_ICON_URL_BASE						"http://mlb.com/images/homepage/team/y2011/logos/%s.png"
#define MLB_COOKIE_TRIGGER_URL					"http://mlb.com"
#define MLB_LOGIN_URL						 	"https://secure.mlb.com/account/topNavLogin.jsp"
#define MLB_LOGOUT_URL 							"https://secure.mlb.com/enterworkflow.do?flowId=registration.logout&c_id=mlb"
#define MLB_WORKFLOW_URL						"http://www.mlb.com/enterworkflow.do?flowId=media.media"

#define MLB_PLAYBACK_URL						"https://secure.mlb.com/pubajaxws/bamrest/MediaService2_0/op-findUserVerifiedEvent/v-2.3?contentId=%s&identityPointId=%s&playbackScenario=%s&fingerprint=%s&eventId=%s&sessionKey=%s"
#define MLB_LOGIN_OPTS							"emailAddress=%s&password=%s"
#define MLB_MAX_XML_LEN			 				1024

#define MLB_XPATH_EPG_GAMES						"/epg/game"
#define MLB_XPATH_GRID_GAME						"/games/game[@calendar_event_id='%s']"
#define MLB_XPATH_GRID_HOMEBASE_MEDIA			"/games/game[@calendar_event_id='%s']/game_media/homebase/media"
#define MLB_XPATH_GRID_HOMEBASE_MEDIA_TYPE		"/games/game[@calendar_event_id='%s']/game_media/homebase/media[@type='%s' and @playback_scenario='%s']"
//#define MLB_XPATH_GRID_HOMEBASE_MEDIA_TYPE2		"/games/game[@calendar_event_id='%s']/game_media/homebase/media[(@type='%s' or @type='%s') and @playback_scenario='%s']"
#define MLB_XPATH_GRID_HOMEBASE_MEDIA_TYPE3		"/games/game[@calendar_event_id='%s']/game_media/homebase/media[(@type='%s' or @type='%s' or @type='%s') and @playback_scenario='%s']"

#define MLB_GRID_XML							"http://gdx.mlb.com/components/game/mlb/year_%04d/month_%02d/day_%02d/grid.xml"
#define MLB_EPG_XML								"http://gdx.mlb.com/components/game/mlb/year_%04d/month_%02d/day_%02d/epg.xml"

#define MLB_SCENARIO						 	"HTTP_CLOUD_WIRED"
#define MLB_AUTHURL								"https://secure.mlb.com/account/topNavLogin.jsp"
#define MLB_LOGOUTURL 							"https://secure.mlb.com/enterworkflow.do?flowId=registration.logout&c_id=mlb"

#define MLB_DEFAULT_BINDIP						"0.0.0.0"
#define MLB_DEFAULT_BINDPORT					8988

#define MLB_MAX_DEV_COUNT						30
#define MLB_DEFAULT_READ_SZ						(TS_PACKET_SIZE * 171)

#define MLB_OVERFLOW_SZ							(TS_PACKET_SIZE * 55000)


#define MLB_XML_EPG								"epg"
#define MLB_XML_EPG_LASTMOD						"last_modified"
#define MLB_XML_EPG_CAL_EVENTID					"calendar_event_id"
#define MLB_XML_EPG_DISP_TZ						"display_time_zone"
#define MLB_XML_EPG_START						"start"
#define MLB_XML_EPG_VENUE						"venue"
#define MLB_XML_EPG_TIME_DATE					"time_date"
#define MLB_XML_EPG_TIME_ZONE					"time_zone"
#define MLB_XML_EPG_TIME_AMPM					"ampm"
#define MLB_XML_EPG_DATA_DIR					"game_data_directory"

#define MLB_XML_EPG_AW_NAME_ABB					"away_name_abbrev"
#define MLB_XML_EPG_AW_CODE						"away_code"
#define MLB_XML_EPG_AW_FILE_CODE				"away_file_code"
#define MLB_XML_EPG_AW_TEAM_ID					"away_team_id"
#define MLB_XML_EPG_AW_TEAM_CITY				"away_team_city"
#define MLB_XML_EPG_AW_TEAM_NAME				"away_team_name"

#define MLB_XML_EPG_HM_NAME_ABB					"home_name_abbrev"
#define MLB_XML_EPG_HM_CODE						"home_code"
#define MLB_XML_EPG_HM_FILE_CODE				"home_file_code"
#define MLB_XML_EPG_HM_TEAM_ID					"home_team_id"
#define MLB_XML_EPG_HM_TEAM_CITY				"home_team_city"
#define MLB_XML_EPG_HM_TEAM_NAME				"home_team_name"

#define MLB_XML_GRID_STATUS						"status"
#define MLB_XML_GRID_HOMEBASE_TYPE				"type"
#define MLB_XML_GRID_HOMEBASE_TYPE_HM			"mlbtv_home"
#define MLB_XML_GRID_HOMEBASE_TYPE_AW			"mlbtv_away"
#define MLB_XML_GRID_HOMEBASE_TYPE_NATIONAL		"mlbtv_national"
#define MLB_XML_GRID_HOMEBASE_DISPLAY			"display"
#define MLB_XML_GRID_HOMEBASE_ID				"id"
#define MLB_XML_GRID_HOMEBASE_BLACKOUT			"blackout"
#define MLB_XML_GRID_HOMEBASE_STATE				"state"
#define MLB_XML_GRID_HOMEBASE_FREE				"free"

#define MLB_CDN_OVERRIDE_NONE					0x00
#define MLB_CDN_OVERRIDE_LEVEL3					0x01
#define MLB_CDN_OVERRIDE_AKAMAI					0x02
#define MLB_CDN_OVERRIDE_CYCLE					0x03
#define MLB_CDN_START_STR						"mlblive-"

uint8_t mlb_destroy(TUNER_STRUCT *);

TUNER_MLB_EVENTID_STRUCT *__mlb_getevent_from_number(TUNER_MLB_STRUCT *, uint8_t);
TUNER_MLB_EVENTID_STRUCT *__mlb_getevent_from_hm_file_code(TUNER_MLB_STRUCT *, char *);
TUNER_MLB_EVENTID_STRUCT *__mlb_getevent_from_aw_file_code(TUNER_MLB_STRUCT *, char *);
TUNER_MLB_EVENTID_STRUCT *__mlb_getevent_from_file_code(TUNER_MLB_STRUCT *, char *);

int _gui_incoming_parse(char *, uint8_t, uint8_t);
int __mlb_getconfig(TUNER_MLB_STRUCT *);
int _mlb_channel_change(TUNER_STRUCT *, TUNER_CHANNEL_STRUCT *);
uint8_t mlb_read_data(TUNER_STRUCT *);
int mlb_tune_channel(TUNER_STRUCT *, TUNER_INITPASS_STRUCT*);
int __mlb_getchan_epg(TUNER_INITPASS_STRUCT *);
int __mlb_getload_games(TUNER_MLB_STRUCT *, uint64_t, uint8_t);
//int __mlb_getchan_count(TUNER_INITPASS_STRUCT *);
void _mlb_switch_cdn(char *, uint8_t);

int mlb_parse_cookies(TUNER_MLB_STRUCT *);
int mlb_cookie_trigger(TUNER_MLB_STRUCT *);
int mlb_wf(TUNER_MLB_STRUCT *);
int mlb_login(TUNER_MLB_STRUCT *);
int mlb_innings_url(TUNER_MLB_STRUCT *, TUNER_MLB_EVENTID_STRUCT *);
int __mlb_getchannel_lineup(TUNER_MLB_STRUCT *);
//uint64_t _mlb_time_str_to_epoch(char *, uint8_t);
uint8_t _mlb_process_ext_x_key(HLS_STREAM_URL *stream);
uint8_t _mlb_post_fetch(HLS_SEGMENT *segment);
uint8_t _mlb_bad_decrypt(HLS_SEGMENT *segment);

void _mlb_set_stream_opts(TUNER_STRUCT *, char *);
char * xml_get_attr(xmlNodePtr, int, char *, int);
char * xml_get_element(xmlNodePtr, int, char *, int);

int mlb_playback(TUNER_MLB_STRUCT *, TUNER_MLB_EVENTID_STRUCT *);
void _mlb_add_b64url_cache(TUNER_MLB_STRUCT *, TUNER_MLB_B64URL_CACHE *);
TUNER_MLB_B64URL_CACHE * _mlb_get_b64url_cache(TUNER_MLB_STRUCT *, char *, char *);
int __mlb_save_skey(TUNER_MLB_STRUCT *);
int __mlb_get_skey(TUNER_MLB_STRUCT *);
uint64_t _mlb_get_first_pts(uint8_t *, uint32_t);

TUNER_MLB_EPG_XML_CACHE *_mlb_get_epg_cache(TUNER_MLB_STRUCT *, int, int, int);
TUNER_MLB_EPG_XML_CACHE *_mlb_add_epg_cache(TUNER_MLB_STRUCT *, int, int, int, UTILS_DATAPAIR_STRUCT *, UTILS_DATAPAIR_STRUCT *);
void _mlb_expire_epg_cache(TUNER_MLB_STRUCT *, uint32_t);

DEVICE_STRUCT *_mlb_get_dev_struct_from_tuner(TUNER_STRUCT *);
TUNER_CHANNEL_STRUCT *_mlb_get_tch_struct_from_tuner(TUNER_STRUCT *);
TUNER_MLB_STRUCT *_mlb_get_mlb_struct_from_tuner(TUNER_STRUCT *);
UTILS_DATAPAIR_STRUCT * _mlb_curl_fetch(CURL *, char *, HLS_FETCH_OPTIONS *);

void _mlb_set_auth_proxy(TUNER_MLB_STRUCT *, char *);
void _mlb_set_fetch_proxy(TUNER_MLB_STRUCT *, char *);


TUNER_MLB_EVENTID_STRUCT *__mlb_sort_game_times(TUNER_MLB_EVENTID_STRUCT *);
void __mlb_swap_eventid(TUNER_MLB_EVENTID_STRUCT *, TUNER_MLB_EVENTID_STRUCT *);

/*
uint8_t _mlb_seek_to_time(MPEGTS_IO_READER_STRUCT *, uint64_t);
uint64_t _mlb_get_end_time(MPEGTS_IO_READER_STRUCT *);
uint64_t _mlb_get_start_time(MPEGTS_IO_READER_STRUCT *);
uint64_t _mlb_get_current_time(MPEGTS_IO_READER_STRUCT *);
*/

/*
uint8_t _mlb_post_decrypt(HLS_SEGMENT *segment)
{
}
*/

uint8_t _mlb_bad_decrypt(HLS_SEGMENT *segment)
{
	if (segment && segment->parent)
	{
		HLS_STREAM_URL * stream = segment->parent;
		HLS_MASTER_URL *master = stream->parent;
		if (master)
		{
			TUNER_MLB_STRUCT *mlbdev = (TUNER_MLB_STRUCT *)master->parent;
			if (mlbdev)
			{
				TUNER_MLB_EVENTID_STRUCT *tmp_event = mlbdev->live_event_struct;
				if (tmp_event)
				{
					uint8_t read_again = 0;
					printf("[MLB] re-authing... \n");

					mlb_cookie_trigger(mlbdev);
					mlb_parse_cookies(mlbdev);
					mlbdev->auth.current_time = time(NULL);

					if (mlbdev->auth.ipid_expire <= mlbdev->auth.current_time)
					{
						mlb_login(mlbdev);
						read_again = 1;
					}
					if (read_again)
					{
						printf("[MLB] Expired Cookie(s), re-auth\n");
						mlb_parse_cookies(mlbdev);
					}
					// call curl_easy_cleanup() to save cookie, then re-init
					curl_easy_cleanup(mlbdev->curl_handle);
					mlbdev->curl_handle = curl_easy_init();
					return mlb_playback(mlbdev, tmp_event);
				}
			}
			else
			{
				printf("[MLB] _mlb_bad_decrypt exit 3\n");
			}
		}
		else
		{
			printf("[MLB] _mlb_bad_decrypt exit 2\n");
		}
	}
	else
	{
		printf("[MLB] _mlb_bad_decrypt exit 1\n");
	}
	return 1;
}

int __mlb_save_skey(TUNER_MLB_STRUCT *dev)
{
	int filehandle = -1, ret = 1;
	if (dev)
	{
		int rc;
		filehandle = open(dev->skey_file, O_TRUNC | O_WRONLY | O_CREAT, OUTPUT_FILE_MODE);
		if (filehandle == -1)
		{
			return 1;
		}

		rc = write(filehandle,  (uint8_t*)&dev->auth.session_key, strlen(dev->auth.session_key));
		if (rc == strlen(dev->auth.session_key))
			ret = 0;
		close(filehandle);
	}
	return ret;
}

int __mlb_get_skey(TUNER_MLB_STRUCT *dev)
{
	int filehandle = -1, ret = 1;
	off_t sz = 0;

	if (dev && file_exists(dev->skey_file) == 0)
	{
		sz = get_file_size(dev->skey_file);
		if (sz > 0 && sz <= sizeof(dev->auth.session_key))
		{
			int bytes_read = 0;

			filehandle = open(dev->skey_file, O_RDONLY);
			if (filehandle)
			{
				lseek(filehandle, 0, SEEK_SET);
				bytes_read = get_bytes_from_file(filehandle, (uint8_t *)&dev->auth.session_key, sz);
				if (bytes_read == sz)
				{
					printf("[MLB] Reading session-key from file: %s\n", dev->auth.session_key);
					ret = 0;
				}
				else
				{
					printf("[MLB] session-key file err -- problem reading\n");
				}
				close(filehandle);
			}
		}
		else
		{
			printf("[MLB] session-key file too big: %" PRId64"\n", sz);
		}
	}
	else
	{
		printf("[MLB] session-key file does not exist: %s\n", dev->skey_file);
	}
	return ret;
}


DEVICE_STRUCT *_mlb_get_dev_struct_from_tuner(TUNER_STRUCT *tuner)
{
	if (tuner && tuner->parent)
	{
		return (DEVICE_STRUCT *)tuner->parent;
	}
	return NULL;
}


TUNER_CHANNEL_STRUCT *_mlb_get_tch_struct_from_tuner(TUNER_STRUCT *tuner)
{
	if (tuner)
	{
		DEVICE_STRUCT *dev = _mlb_get_dev_struct_from_tuner(tuner);
		if (dev)
			return device_channel_get_struct(dev, tuner->tuned_channel, tuner->tuned_channel);
	}
	return NULL;
}

TUNER_MLB_STRUCT *_mlb_get_mlb_struct_from_tuner(TUNER_STRUCT *tuner)
{
	if (tuner)
	{
		DEVICE_STRUCT *dev = _mlb_get_dev_struct_from_tuner(tuner);
		if (dev && dev->parent)
		{
			TUNER_CHANNEL_STRUCT * channel = _mlb_get_tch_struct_from_tuner(tuner);
			if (channel)
			{
				return (TUNER_MLB_STRUCT *)dev->parent;
			}
		}
	}
	return NULL;
}
/*
uint8_t _mlb_seek_to_time(MPEGTS_IO_READER_STRUCT *reader, uint64_t dts)
{
	printf("!!!!!!!!! \t _mlb_seek_to_time - %" PRId64"\n", dts);
	if (reader && reader->parent && dts != MPEGTS_IO_INVALID)
	{
		MPEGTS_IO_STRUCT *mpegts_io = reader->parent;
		if (mpegts_io->extra_data2)
		{
			TUNER_STRUCT *tuner = (TUNER_STRUCT *)mpegts_io->extra_data2;
//			DEVICE_STRUCT *dev = _mlb_get_dev_struct_from_tuner(tuner);
			TUNER_CHANNEL_STRUCT *channel = _mlb_get_tch_struct_from_tuner(tuner);

			if (channel)
			{
				TUNER_MLB_STRUCT * mlbdev = _mlb_get_mlb_struct_from_tuner(tuner);

				if (mlbdev)
				{
					// 1
					TUNER_MLB_EVENTID_STRUCT *tmp_event = __mlb_getevent_from_file_code(mlbdev, channel->info);
//					mlbdev->read_overflow && mlbdev->read_overflow_read_pos
					mlbdev->read_overflow_read_pos = 0;

					if (tmp_event && tmp_event->hls)
					{
						HLS_MASTER_URL * master = tmp_event->hls;
						if (master->active_stream && master->active_stream->first_segment)
						{
							HLS_STREAM_URL *stream = master->active_stream;
							HLS_SEGMENT *segment = stream->first_segment;
							double _tc = (dts/90000) +  mlbdev->playlist_stream_offset;

							printf("_mlb_seek_to_time, new tc: %0.2f\n", _tc);
							pthread_mutex_lock(&mlbdev->seek_mutex);

							while(segment && segment->next)
							{
								if (_tc >= segment->timecode && _tc < segment->next->timecode)
								{
									printf("Found segment timecode: %0.2f (%0.2f)\n", segment->timecode, _tc);
									stream->active_segment = segment;
									mlbdev->need_sync_tc = segment->timecode;
									mlbdev->read_overflow_read_pos = 0;
									break;
								}
								segment = segment->next;
							}
							pthread_mutex_unlock(&mlbdev->seek_mutex);

//							uint64_t ret = 0;
//							ret = floor((stream->start_time - mlbdev->playlist_stream_offset) * 90000);
//							printf("[MLB] _mlb_get_start_time, diff: %0.2f, MPEG-TS Time (PTS, 90k): %" PRId64" -- From Playlist: %0.2f -- return: %" PRId64"\n", mlbdev->playlist_stream_offset, mlbdev->pts_time, mlbdev->playlist_time, ret);
//							return ret;
						}
					}
				}
			}
		}
	}
	return 1;
}

uint64_t _mlb_get_current_time(MPEGTS_IO_READER_STRUCT *reader)
{
	uint64_t ret = MPEGTS_IO_INVALID;
//	printf("_mlb_get_current_time - 1\n");
	if (reader && reader->parent)
	{
		MPEGTS_IO_STRUCT *mpegts_io = reader->parent;
		if (mpegts_io->extra_data2)
		{
			TUNER_STRUCT *tuner = (TUNER_STRUCT *)mpegts_io->extra_data2;
//			DEVICE_STRUCT *dev = _mlb_get_dev_struct_from_tuner(tuner);
			TUNER_CHANNEL_STRUCT *channel = _mlb_get_tch_struct_from_tuner(tuner);

			if (channel)
			{
				TUNER_MLB_STRUCT * mlbdev = _mlb_get_mlb_struct_from_tuner(tuner);

				if (mlbdev)
				{
//					printf("\t[MLB] _mlb_get_end_time, diff: %0.2f, MPEG-TS Time (PTS, 90k): %" PRId64" -- From Playlist: %0.2f\n", mlbdev->playlist_stream_offset, mlbdev->pts_time, mlbdev->playlist_time);
			//		printf("_mlb_get_start_time, playlist/pts offset: %0.2f -- %" PRId64" (%" PRId64")\n", mlbdev->playlist_stream_offset, mlbdev->first_timecode, mlbdev->first_pts, mpegts_io->first_fileunit->first_pts );
					// 2
					TUNER_MLB_EVENTID_STRUCT *tmp_event = __mlb_getevent_from_file_code(mlbdev, channel->info);
					if (tmp_event && tmp_event->hls)
					{
						HLS_MASTER_URL * master = tmp_event->hls;

						if (master->active_stream && master->active_stream->curent_segment)
						{
//							uint64_t ret = 0;
							HLS_STREAM_URL *stream = master->active_stream;
							HLS_SEGMENT *segment = stream->active_segment;

							if (segment->timecode)
							{
								ret = floor((segment->timecode - mlbdev->playlist_stream_offset) * 90000);
//								printf("[MLB] _mlb_get_end_time, offset: %0.2f -- return: %" PRId64" (i: %"PRId64")\n", mlbdev->playlist_stream_offset, ret, segment->index);

							}
							else
							{
								printf("[MLB] Segment has no timecode :(\n");
							}
//							printf("[MLB] _mlb_get_start_time, diff: %0.2f, MPEG-TS Time (PTS, 90k): %" PRId64" -- From Playlist: %0.2f -- return: %" PRId64"\n", mlbdev->playlist_stream_offset, mlbdev->pts_time, mlbdev->playlist_time, ret);
						}
					}
				}
			}
		}
	}
	printf("[MLB] _mlb_get_current_time, ret: %" PRId64"\n", ret);
	return ret;
}

uint64_t _mlb_get_end_time(MPEGTS_IO_READER_STRUCT *reader)
{
	uint64_t ret = MPEGTS_IO_INVALID;
//	printf("_mlb_get_end_time - 1\n");

	if (reader && reader->parent)
	{
		MPEGTS_IO_STRUCT *mpegts_io = reader->parent;
		if (mpegts_io->extra_data2)
		{
			TUNER_STRUCT *tuner = (TUNER_STRUCT *)mpegts_io->extra_data2;
//			DEVICE_STRUCT *dev = _mlb_get_dev_struct_from_tuner(tuner);
			TUNER_CHANNEL_STRUCT *channel = _mlb_get_tch_struct_from_tuner(tuner);

			if (channel)
			{
				TUNER_MLB_STRUCT * mlbdev = _mlb_get_mlb_struct_from_tuner(tuner);

				if (mlbdev)
				{
					printf("\t[MLB] _mlb_get_end_time, diff: %0.2f, MPEG-TS Time (PTS, 90k): %" PRId64" -- From Playlist: %0.2f\n", mlbdev->playlist_stream_offset, mlbdev->pts_time, mlbdev->playlist_time);
			//		printf("_mlb_get_start_time, playlist/pts offset: %0.2f -- %" PRId64" (%" PRId64")\n", mlbdev->playlist_stream_offset, mlbdev->first_timecode, mlbdev->first_pts, mpegts_io->first_fileunit->first_pts );
					// 3
					TUNER_MLB_EVENTID_STRUCT *tmp_event = __mlb_getevent_from_file_code(mlbdev, channel->info);
					if (tmp_event && tmp_event->hls)
					{
						HLS_MASTER_URL * master = tmp_event->hls;

						if (master->active_stream && master->active_stream->curent_segment)
						{
//							uint64_t ret = 0;
							HLS_STREAM_URL *stream = master->active_stream;
							HLS_SEGMENT *segment = stream->first_segment;

							while(segment->next)
							{
								segment = segment->next;
							}

							if (segment->timecode)
							{
								ret = floor((segment->timecode - mlbdev->playlist_stream_offset) * 90000);
//								printf("[MLB] _mlb_get_end_time, offset: %0.2f -- return: %" PRId64" (i: %"PRId64")\n", mlbdev->playlist_stream_offset, ret, segment->index);

							}
							else
							{
								printf("[MLB] Segment has no timecode :(\n");
							}
//							printf("[MLB] _mlb_get_start_time, diff: %0.2f, MPEG-TS Time (PTS, 90k): %" PRId64" -- From Playlist: %0.2f -- return: %" PRId64"\n", mlbdev->playlist_stream_offset, mlbdev->pts_time, mlbdev->playlist_time, ret);
						}
					}
				}
			}
		}
	}
//	printf("[MLB] _mlb_get_end_time, ret: %" PRId64"\n", ret);
	return ret;
}

uint64_t _mlb_get_start_time(MPEGTS_IO_READER_STRUCT *reader)
{
//	printf("_mlb_get_start_time - 1\n");
	if (reader && reader->parent)
	{
		MPEGTS_IO_STRUCT *mpegts_io = reader->parent;
		if (mpegts_io->extra_data2)
		{
			TUNER_STRUCT *tuner = (TUNER_STRUCT *)mpegts_io->extra_data2;
//			DEVICE_STRUCT *dev = _mlb_get_dev_struct_from_tuner(tuner);
			TUNER_CHANNEL_STRUCT *channel = _mlb_get_tch_struct_from_tuner(tuner);

			if (channel)
			{
				TUNER_MLB_STRUCT * mlbdev = _mlb_get_mlb_struct_from_tuner(tuner);

				if (mlbdev)
				{
					printf("\t[MLB] _mlb_get_start_time, diff: %0.2f, MPEG-TS Time (PTS, 90k): %" PRId64" -- From Playlist: %0.2f\n", mlbdev->playlist_stream_offset, mlbdev->pts_time, mlbdev->playlist_time);
			//		printf("_mlb_get_start_time, playlist/pts offset: %0.2f -- %" PRId64" (%" PRId64")\n", mlbdev->playlist_stream_offset, mlbdev->first_timecode, mlbdev->first_pts, mpegts_io->first_fileunit->first_pts );
					// 4
					TUNER_MLB_EVENTID_STRUCT *tmp_event = __mlb_getevent_from_file_code(mlbdev, channel->info);
					if (tmp_event && tmp_event->hls)
					{
						HLS_MASTER_URL * master = tmp_event->hls;
						if (master->active_stream)
						{
							HLS_STREAM_URL *stream = master->active_stream;
							uint64_t ret = 0;
							ret = floor((stream->start_time - mlbdev->playlist_stream_offset) * 90000);
							printf("[MLB] _mlb_get_start_time, diff: %0.2f, MPEG-TS Time (PTS, 90k): %" PRId64" -- From Playlist: %0.2f -- return: %" PRId64"\n", mlbdev->playlist_stream_offset, mlbdev->pts_time, mlbdev->playlist_time, ret);
							return ret;
						}
					}
				}
			}
		}
	}
	return MPEGTS_IO_INVALID;
}
*/

TUNER_MLB_EVENTID_STRUCT *__mlb_getevent_from_number(TUNER_MLB_STRUCT * mlbdev, uint8_t num)
{
	TUNER_MLB_EVENTID_STRUCT * ret = NULL;
	if (mlbdev)
	{
		TUNER_MLB_EVENTID_STRUCT * tmp_event = NULL;
		int i, j;

		if (num > mlbdev->games_total_count)
			return NULL;

		for(i=0; i < mlbdev->games_count; i++)
		{
			for(j=0; j < mlbdev->games[i].events_count; j++)
			{
				tmp_event = &mlbdev->games[i].events[j];
				if (tmp_event && tmp_event->game_number == num)
					return tmp_event;
			}
		}
	}
	return ret;
}


void __mlb_swap_eventid(TUNER_MLB_EVENTID_STRUCT *a, TUNER_MLB_EVENTID_STRUCT *b)
{
	if (a && b)
	{
		TUNER_MLB_EVENTID_STRUCT copy = {0};
		TUNER_MLB_EVENTID_STRUCT * n1 = a->next;
		TUNER_MLB_EVENTID_STRUCT * n2 = b->next;
		memcpy(&copy, a, sizeof(TUNER_MLB_EVENTID_STRUCT));
		memcpy(a, b, sizeof(TUNER_MLB_EVENTID_STRUCT));
		memcpy(b, &copy, sizeof(TUNER_MLB_EVENTID_STRUCT));
		a->next = n1;
		b->next = n2;
	}
	return;
}


TUNER_MLB_EVENTID_STRUCT *__mlb_sort_game_times(TUNER_MLB_EVENTID_STRUCT * first)
{
	TUNER_MLB_EVENTID_STRUCT * ret = first;
/*
	if (first)
	{
		TUNER_MLB_EVENTID_STRUCT * a = NULL;
		TUNER_MLB_GAME_STRUCT *game1 = NULL, *game2 = NULL;
		a = ret = first;

		while(a && a->next)
		{
			game1 = a->parent;
			game2 = a->next->parent;
			if (game2->start_epoch < game1->start_epoch)
			{
				__mlb_swap_eventid(a, a->next);
				a = first;
				continue;
			}
			a = a->next;
		}
		ret = a;
	}
*/
	return ret;
}

TUNER_MLB_EVENTID_STRUCT *__mlb_getevent_from_hm_file_code(TUNER_MLB_STRUCT *mlbdev, char *code)
{
	TUNER_MLB_EVENTID_STRUCT * ret = NULL;
	if (mlbdev && code)
	{
		TUNER_MLB_EVENTID_STRUCT * tmp_event = NULL, *tmp2 = NULL;
		TUNER_MLB_GAME_STRUCT *game = NULL;
		int i, j;

		for(i=0; i < mlbdev->games_count; i++)
		{
			for(j=0; j < mlbdev->games[i].events_count; j++)
			{
				tmp_event = &mlbdev->games[i].events[j];
				if (tmp_event)
				{
					game = tmp_event->parent;
//					printf("HM: [%c %c %c] [%c %c %c] [COMPARE] [%c %c %c]\n", game->home_team_file_code[0], game->home_team_file_code[1], game->home_team_file_code[2], game->away_team_file_code[0], game->away_team_file_code[1], game->away_team_file_code[2], code[0], code[1], code[2]);
					if (game)
					{
					 	if (game->home_team_file_code[0] == code[0] &&
							game->home_team_file_code[1] == code[1] &&
							game->home_team_file_code[2] == code[2])
						{

//							printf("\tHERE: [%s] [%s]\n", tmp_event->type, MLB_XML_GRID_HOMEBASE_TYPE_HM);
							if (strcmp(tmp_event->type, MLB_XML_GRID_HOMEBASE_TYPE_HM) == 0 ||
								strcmp(tmp_event->type, MLB_XML_GRID_HOMEBASE_TYPE_NATIONAL) == 0)
							{
//								printf("ReturnHM: %s, id: %s\n", tmp_event->display, tmp_event->id);
								if (!ret)
								{
									tmp2 = ret = tmp_event;
								}
								else
								{
									tmp2->next = tmp_event;
									tmp2 = tmp2->next;
								}
								//return tmp_event;
							}
						}
					}
				}
			}
		}
	}
		if (ret)
			__mlb_sort_game_times(ret);
	return ret;
}

TUNER_MLB_EVENTID_STRUCT *__mlb_getevent_from_aw_file_code(TUNER_MLB_STRUCT *mlbdev, char *code)
{
	TUNER_MLB_EVENTID_STRUCT * ret = NULL;
	if (mlbdev && code)
	{
		TUNER_MLB_EVENTID_STRUCT * tmp_event = NULL, *tmp2 = NULL;
		TUNER_MLB_GAME_STRUCT *game = NULL;
		int i, j;

		for(i=0; i < mlbdev->games_count; i++)
		{
			for(j=0; j < mlbdev->games[i].events_count; j++)
			{
				tmp_event = &mlbdev->games[i].events[j];
				if (tmp_event)
				{
					game = tmp_event->parent;
					if (game)
					{
//					printf("HM: [%c %c %c] [%c %c %c]\n", game->home_team_file_code[0], game->home_team_file_code[1], game->home_team_file_code[2], game->away_team_file_code[0], game->away_team_file_code[1], game->away_team_file_code[2]);
						if (game->away_team_file_code[0] == code[0] &&
							game->away_team_file_code[1] == code[1] &&
							game->away_team_file_code[2] == code[2])
						{
							if (strcmp(tmp_event->type, MLB_XML_GRID_HOMEBASE_TYPE_AW) == 0)
							{
								if (!ret)
								{
									tmp2 = ret = tmp_event;
								}
								else
								{
									tmp2->next = tmp_event;
									tmp2 = tmp2->next;
								}
//								printf("ReturnAW: %s, id: %s\n", tmp_event->display, tmp_event->id);
								//return tmp_event;
							}
						}
					}
				}
			}
		}
	}
		if (ret)
			__mlb_sort_game_times(ret);
	return ret;
}


TUNER_MLB_EVENTID_STRUCT *__mlb_getevent_from_file_code(TUNER_MLB_STRUCT *mlbdev, char *code)
{
	TUNER_MLB_EVENTID_STRUCT * ret = NULL;

	if (mlbdev && code)
	{
		ret = __mlb_getevent_from_hm_file_code(mlbdev, code);
		if (!ret)
			ret = __mlb_getevent_from_aw_file_code(mlbdev, code);

		if (ret)
			__mlb_sort_game_times(ret);
	}
	return ret;
}


int _gui_incoming_parse(char *ip, uint8_t type, uint8_t cmd)
{
//	printf("I was called: %s [%d %d]\n", ip, type, cmd);
	return 0;
}

void _mlb_set_auth_proxy(TUNER_MLB_STRUCT *mlbdev, char *proxy)
{
	if (mlbdev)
	{
		mlbdev->auth_proxy_type = 0;
		memset(mlbdev->auth_proxy, 0, sizeof(mlbdev->auth_proxy));

		if (proxy && strlen(proxy))
		{
			snprintf(mlbdev->auth_proxy, sizeof(mlbdev->auth_proxy) -1 , "%s", proxy);
			mlbdev->auth_proxy_type = HLS_PROXY_TYPE_HTTP;

			if (strlen(mlbdev->auth_proxy)  > 5)
			{
				if (mlbdev->auth_proxy[0] == 's' && mlbdev->auth_proxy[1] == 'o' &&
					mlbdev->auth_proxy[2] == 'c' && mlbdev->auth_proxy[3] == 'k')
				{
					mlbdev->auth_proxy_type = HLS_PROXY_TYPE_SOCKS;
				}
			}
		}
	}
}

void _mlb_set_fetch_proxy(TUNER_MLB_STRUCT *mlbdev, char *proxy)
{
	if (mlbdev)
	{
		mlbdev->fetch_proxy_type = 0;
		memset(mlbdev->fetch_proxy, 0, sizeof(mlbdev->fetch_proxy));

		if (proxy && strlen(proxy))
		{
			snprintf(mlbdev->fetch_proxy, sizeof(mlbdev->fetch_proxy) -1 , "%s", proxy);
			mlbdev->fetch_proxy_type = HLS_PROXY_TYPE_HTTP;

			if (strlen(mlbdev->fetch_proxy)  > 5)
			{
				if (mlbdev->fetch_proxy[0] == 's' && mlbdev->fetch_proxy[1] == 'o' &&
					mlbdev->fetch_proxy[2] == 'c' && mlbdev->fetch_proxy[3] == 'k')
				{
					mlbdev->fetch_proxy_type = HLS_PROXY_TYPE_SOCKS;
				}
			}
		}
	}
}

void _mlb_set_stream_opts(TUNER_STRUCT *tuner, char *str)
{
	if (tuner && tuner->parent && str)
	{
		DEVICE_STRUCT *dev = (DEVICE_STRUCT *)tuner->parent;
		TUNER_MLB_STRUCT *mlbdev = (TUNER_MLB_STRUCT *)dev->parent;


		char *tmpstr = strdup(str);
		if (tmpstr)
		{
			char *sep = strstr(tmpstr, "=");
			if (!sep)
			{
				printf("[MLB] Not a setting/value pair: %s, igorning\n", tmpstr);
				free(tmpstr);
				return;
			}

			char * setting = tmpstr;
			char * value = sep+1;
			sep[0] = '\0';

			if (!setting || !value)
			{
				printf("[MLB] Not a setting/value pair: %s, igorning\n", setting);
			}
			else if (strstr(tmpstr, TUNER_MLB_PARAM_START_BITRATE))
			{
				mlbdev->start_bitrate = strtol(value, (char **)NULL, 10);
//				printf("[MLB] Setting start bitrate: %d\n", mlbdev->start_bitrate);
			}
			else if (strstr(tmpstr, TUNER_MLB_PARAM_MIN_BITRATE))
			{
				mlbdev->min_bitrate = strtol(value, (char **)NULL, 10);
//				printf("[MLB] Setting min bitrate: %d\n", mlbdev->min_bitrate);
			}
			else if (strstr(tmpstr, TUNER_MLB_PARAM_MAX_BITRATE))
			{
				mlbdev->max_bitrate = strtol(value, (char **)NULL, 10);
//				printf("[MLB] Setting max bitrate: %d\n", mlbdev->max_bitrate);
			}
			else if (strstr(tmpstr, TUNER_MLB_PARAM_GAME_DATETIME))
			{
				mlbdev->game_datetime = strtol(value, (char **)NULL, 10);
//				printf("[MLB] Setting game date: %" PRId64"\n", mlbdev->game_datetime);
			}
			else if (strstr(tmpstr, TUNER_MLB_PARAM_ADAPTIVE_BITRATE))
			{
				mlbdev->adaptive = atoi(value);
//				printf("[MLB] Setting adaptive: %d\n", mlbdev->adaptive);
			}
			else if (strstr(tmpstr, TUNER_MLB_PARAM_GAME_STARTTIME))
			{
				mlbdev->game_starttime = strtol(value, (char **)NULL, 10);
//				printf("[MLB] Setting game start time: %" PRId64"\n", mlbdev->game_starttime);
			}
			else if (strstr(tmpstr, TUNER_MLB_PARAM_AUTH_PROXY))
			{
				_mlb_set_auth_proxy(mlbdev, value);
			}
			else if (strstr(tmpstr, TUNER_MLB_PARAM_FETCH_PROXY))
			{
				_mlb_set_fetch_proxy(mlbdev, value);
			}
			else if (strstr(tmpstr, TUNER_MLB_PARAM_FORCE_CDN))
			{
				mlbdev->force_cdn = atoi(value);
			}
			else
			{
				printf("[MLB] Unknown setting: %s\n", setting);
			}
			free(tmpstr);
		}

	}
	return;
}

void _mlb_switch_cdn(char *url, uint8_t cdn)
{
	if (cdn)
	{
		uint8_t current_cdn = MLB_CDN_OVERRIDE_NONE;
		int startpos = strlen(MLB_CDN_START_STR);
		char * replace = strstr(url, MLB_CDN_START_STR);

		if (replace && strlen(replace) > startpos + 4)
		{
			if (replace[startpos] == 'a' && replace[startpos+1] == 'k' && replace[startpos+2] == 'c')
			{
				current_cdn = MLB_CDN_OVERRIDE_AKAMAI;
			}
			else if (replace[startpos] == 'l' && replace[startpos+1] == '3' && replace[startpos+2] == 'c')
			{
				current_cdn = MLB_CDN_OVERRIDE_LEVEL3;
			}
			else
			{
				printf("!!ACK: %s\n", replace);
			}

			if (cdn == MLB_CDN_OVERRIDE_CYCLE)
			{
				if (current_cdn == MLB_CDN_OVERRIDE_AKAMAI)
				{
					cdn = MLB_CDN_OVERRIDE_LEVEL3;
				}
				else if (current_cdn == MLB_CDN_OVERRIDE_LEVEL3)
				{
					cdn = MLB_CDN_OVERRIDE_AKAMAI;
				}
			}

			if (current_cdn && current_cdn != cdn)
			{
				printf("[MLB] Overrding CDN: %s\n", url);
				if (cdn == MLB_CDN_OVERRIDE_AKAMAI)
				{
					replace[startpos] = 'a';
					replace[startpos+1] = 'k';
				}
				else if (cdn == MLB_CDN_OVERRIDE_LEVEL3)
				{
					replace[startpos] = 'l';
					replace[startpos+1] = '3';
				}
				printf("[MLB] New CDN: %s\n", url);
			}
		}
	}
}


int mlb_tune_channel(TUNER_STRUCT *tuner, TUNER_INITPASS_STRUCT *tpass)
{
//	printf("mlb_tune_channel Start: %d\n", chan);
	int ret = TUNER_TUNE_ERROR_NOCHAN;

	if (tuner && tuner->parent && tpass)
	{
		uint32_t chan = tpass->channel_num;
		DEVICE_STRUCT *dev = (DEVICE_STRUCT *)tuner->parent;
		TUNER_CHANNEL_STRUCT * channel = device_channel_get_struct(dev, chan, chan);
		TUNER_MLB_STRUCT *mlbdev = (TUNER_MLB_STRUCT *)dev->parent;
		PROTOCOL_TUNE_STRUCT * _ptmp = (PROTOCOL_TUNE_STRUCT *)tpass->dataptr;

		if (mlbdev)
		{
			mlbdev->start_bitrate = 0;
			mlbdev->min_bitrate = 0;
			mlbdev->max_bitrate = 0;
			mlbdev->game_datetime = 0;
			mlbdev->adaptive = 0;
			mlbdev->force_cdn = 0;
			mlbdev->game_starttime = 0;
			mlbdev->auth_proxy_type = 0;
			mlbdev->fetch_proxy_type = 0;
			mlbdev->last_auth_time = 0;
			mlbdev->reauth_time = MLB_DEFAULT_REAUTH_TIME;

			memset(mlbdev->auth_proxy, 0, sizeof(mlbdev->auth_proxy));
			memset(mlbdev->fetch_proxy, 0, sizeof(mlbdev->fetch_proxy));

			if (_ptmp)
			{
				if (_ptmp->starting_bandwidth)
					mlbdev->start_bitrate = _ptmp->starting_bandwidth;
				else
					mlbdev->start_bitrate = 3000000;

				mlbdev->min_bitrate = _ptmp->min_bandwidth;
				mlbdev->max_bitrate = _ptmp->max_bandwidth;
				mlbdev->game_datetime = _ptmp->game_datetime;
				mlbdev->adaptive = _ptmp->adaptive;
				mlbdev->force_cdn = _ptmp->cdn;
				mlbdev->game_starttime = _ptmp->game_starttime;
				_mlb_set_auth_proxy(mlbdev, _ptmp->aproxy);
				_mlb_set_fetch_proxy(mlbdev, _ptmp->fproxy);
			}
			else if (tpass->url_params)
			{
				char *token = strtok(tpass->url_params, "&");
				while(token)
				{
					_mlb_set_stream_opts(tuner, token);
					token = strtok(NULL, "&");
				}

				free(tpass->url_params);
				tpass->url_params = NULL;
			}
		}
		else
		{
			printf("MLBDEV IS NULL!!!!!!!\n");
		}

		if (file_exists(_FORCE_FILE_AUTH_PROXY) == 0)
		{
			int filehandle = -1, bytes_read;
			off_t _sz = get_file_size(_FORCE_FILE_AUTH_PROXY);
			filehandle = open(_FORCE_FILE_AUTH_PROXY, O_RDONLY);
			if (filehandle)
			{
				uint8_t *_mem = calloc(1, _sz);
				if (_mem)
				{
					lseek(filehandle, 0, SEEK_SET);
					bytes_read = get_bytes_from_file(filehandle, _mem, _sz);
					if (bytes_read == _sz)
					{
						int b;
						for(b=0; b < _sz; b++)
						{
							if(isprint(_mem[b]) == 0)
							{
								_mem[b] = '\0';
								break;
							}
						}
						_mlb_set_auth_proxy(mlbdev, (char*)_mem);
//						printf("[MLB] force_authproxy: %s\n", mlbdev->auth_proxy);
					}
					free(_mem);
				}
				close(filehandle);
			}

		}

		if (channel)
		{
			int i;
			printf("[MLB] MLB.tv HLS Settings:\n");

			if (strlen(mlbdev->auth_proxy))
				printf("[MLB]\tSetting auth proxy: %s (type: %d)\n", mlbdev->auth_proxy, mlbdev->auth_proxy_type);

			if (strlen(mlbdev->fetch_proxy))
				printf("[MLB]\tSetting fetch proxy: %s (type: %d)\n", mlbdev->fetch_proxy, mlbdev->fetch_proxy_type);

			if (mlbdev->force_cdn)
			{
				if (mlbdev->force_cdn == MLB_CDN_OVERRIDE_LEVEL3)
					printf("[MLB]\tForcing CDN to be Level3\n");
				else if (mlbdev->force_cdn == MLB_CDN_OVERRIDE_AKAMAI)
					printf("[MLB]\tForcing CDN to be Akamai\n");
				else
					printf("[MLB]\tForcing CDN to be unknown?\n");
			}

			printf("[MLB]\tSetting adaptive bitrate: %d\n", mlbdev->adaptive);
			printf("[MLB]\tSetting start bitrate: %d\n", mlbdev->start_bitrate);
			printf("[MLB]\tSetting min bitrate: %d\n", mlbdev->min_bitrate);
			printf("[MLB]\tSetting max bitrate: %d\n", mlbdev->max_bitrate);

			if (mlbdev->game_datetime)
			{
				printf("[MLB]\tDate/Time of game: %s (%" PRId64")\n", utils_epoch_to_str(mlbdev->game_datetime),  mlbdev->game_datetime);
			}
			else
			{
				printf("[MLB]\tDate/Time of game to now\n") ;
				mlbdev->game_datetime = (uint64_t) time(NULL);
			}

//			printf("[MLB]\tSetting game date: %" PRId64"\n", mlbdev->game_datetime);
			printf("[MLB]\tSetting game start time: %" PRId64"\n", mlbdev->game_starttime);

			if (mlbdev && __mlb_getload_games(mlbdev, mlbdev->game_datetime, 0) == 0)
			{
				TUNER_MLB_GAME_STRUCT *game = NULL;
				// 5
				TUNER_MLB_EVENTID_STRUCT *tmp_event2 = __mlb_getevent_from_file_code(mlbdev, channel->info);
				TUNER_MLB_EVENTID_STRUCT *tmp_event = tmp_event2;
                uint64_t now = time(NULL);

				if (tmp_event2->next && now >  tmp_event2->next->parent->start_epoch)
				{
                    printf("[MLB] Double header? Looks like we're past the 2nd game start time.  Let's watch it!\n");
                    tmp_event = tmp_event2->next;
/*
                    printf("NOW: %" PRId64"\n", now);
					printf("8*********\t\t HERE1: %" PRId64" (%s)\n", tmp_event2->parent->start_epoch, tmp_event2->parent->calendar_event_id);
					tmp_event2 = tmp_event2->next;

					while(tmp_event2)
					{
						game = tmp_event2->parent;
						printf("8*********\t\t HERE2: %" PRId64" (%s)\n", game->start_epoch, game->calendar_event_id);

						if (mlbdev->game_datetime >= game->start_epoch)
						{
							printf("\tMMMMMM\n");
//							tmp_event = tmp_event2;
						}
						tmp_event2 = tmp_event2->next;

					}
					game = NULL;
//					tmp_event = tmp_event2->next;
*/
				}

				if (tmp_event)
				{
//							printf("\tMMMMMM2: %" PRId64"\n", tmp_event);
					uint8_t read_again = 0, need_info = 0;
					TUNER_MLB_B64URL_CACHE _cache = {{0}}, *_cache2 = NULL;
					game = tmp_event->parent;

					if (tmp_event->hls)
					{
						HLS_MASTER_URL * master = tmp_event->hls;
						if (master)
						{
							hls_set_bitrate(master, mlbdev->start_bitrate);
						}
					}
					if (!mlbdev->live_event_struct)
						mlbdev->live_event_struct = calloc(1, sizeof(TUNER_MLB_EVENTID_STRUCT));
//					memcpy(mlbdev->live_event_struct, tmp_event, sizeof(TUNER_MLB_EVENTID_STRUCT));

					_cache2 = _mlb_get_b64url_cache(mlbdev, game->calendar_event_id, tmp_event->id);
					if (_cache2)
					{
						printf("[MLB] Using cached B64 URL\n");

						strncpy(game->innings.innings_url, _cache2->innings_url,  sizeof(_cache.innings_url));
						strncpy((char*)tmp_event->b64_url, (char*)_cache2->b64_url, TUNER_MLB_MAX_STR_LEN);
						strncpy(game->calendar_event_id, _cache2->calendar_event_id, sizeof(game->calendar_event_id));
						strncpy(tmp_event->id, _cache2->id, sizeof(tmp_event->id));
//						strncpy(game->innings.start_str, _cache.start_str, sizeof(game->innings.start_str));
					}
					else
					{
						printf("[MLB] Not using cached B64 URL\n");

						mlb_cookie_trigger(mlbdev);
						mlb_parse_cookies(mlbdev);
						mlbdev->auth.current_time = time(NULL);

						if (mlbdev->auth.ipid_expire <= mlbdev->auth.current_time)
						{
							mlb_login(mlbdev);
							read_again = 1;
						}

						if (read_again)
						{
							printf("[MLB] Expired Cookie(s), re-auth\n");
							mlb_parse_cookies(mlbdev);
						}
						// call curl_easy_cleanup() to save cookie, then re-init
						curl_easy_cleanup(mlbdev->curl_handle);
						mlbdev->curl_handle = curl_easy_init();
						need_info = mlb_playback(mlbdev, tmp_event);

						if (need_info == 0)
						{
							printf("HERE: %s\n", game->innings.innings_url);
							strncpy(_cache.innings_url, game->innings.innings_url, sizeof(_cache.innings_url));
							strncpy((char*)_cache.b64_url, (char*)tmp_event->b64_url, sizeof(_cache.b64_url));
							strncpy(_cache.calendar_event_id, game->calendar_event_id, sizeof(_cache.calendar_event_id));
							strncpy(_cache.id, tmp_event->id, sizeof(_cache.id));
//							strncpy(_cache.start_str, game->innings.start_str, sizeof(_cache.start_str));
							_cache.last_update = time(NULL);
							_mlb_add_b64url_cache(mlbdev, &_cache);
						}
					}
					printf("[%d] [%s] [%s vs %s @ %s] [%s] Display: %s, ID: %s, free: %s, blackout: %s [%s]\n",
					tmp_event->game_number,
					game->calendar_event_id,
					game->away_team_name,
					game->home_team_name,
					game->venue,
					game->status,
					tmp_event->display,
					tmp_event->id,
					tmp_event->free,
					tmp_event->blackout,
					tmp_event->b64_url);
					if (need_info == 0)
					{
						double stime = 0;
						HLS_SEGMENT *segment = NULL;

						char *tmpstr = strdup((char*)tmp_event->b64_url);
//						printf("[MLB] Fetching innings info..\n");
						mlb_innings_url(mlbdev, tmp_event);
//						printf("[MLB] innings info done\n");
 //						printf("Base64_URL: %s\n", tmp_event->b64_url);
						if (tmpstr)
						{
							int len = 0;
							len = b64decode(tmpstr);
							if (len)
							{
								uint8_t _free_url = 0;

								char * _url = strtok(tmpstr, "|");
								char * _key = strtok(NULL, "|");
								char * _param = strtok(NULL, "|");

								if (_url)
								{
									if (mlbdev->force_cdn)
									{
										_mlb_switch_cdn(_url, mlbdev->force_cdn);
/*
										uint8_t current_cdn = MLB_CDN_OVERRIDE_NONE;
										int startpos = strlen(MLB_CDN_START_STR);
										char *_new_url = strdup(_url);

										_url = _new_url;

										char * replace = strstr(_url, MLB_CDN_START_STR);
										_free_url = 1;

										if (replace && strlen(replace) > startpos + 4)
										{
											if (replace[startpos] == 'a' && replace[startpos+1] == 'k' && replace[startpos+2] == 'c')
											{
												current_cdn = MLB_CDN_OVERRIDE_AKAMAI;
											}
											else if (replace[startpos] == 'l' && replace[startpos+1] == '3' && replace[startpos+2] == 'c')
											{
												current_cdn = MLB_CDN_OVERRIDE_LEVEL3;
											}
											else
											{
												printf("!!ACK: %s\n", replace);
											}

											if (current_cdn && current_cdn != mlbdev->force_cdn)
											{
												printf("[MLB] Overrding CDN: %s\n", _url);
												if (mlbdev->force_cdn == MLB_CDN_OVERRIDE_AKAMAI)
												{
													replace[startpos] = 'a';
													replace[startpos+1] = 'k';
												}
												else if (mlbdev->force_cdn == MLB_CDN_OVERRIDE_LEVEL3)
												{
													replace[startpos] = 'l';
													replace[startpos+1] = '3';
												}
												printf("[MLB] New CDN: %s\n", _url);

											}
										}
*/
									}

									tmp_event->hls = hls_init(_url, (void*)mlbdev);
									if (_free_url)
										free(_url);

									if (tmp_event->hls)
									{
										tmp_event->hls->extra_data = (void*)tmp_event;
										tmp_event->hls->cb_process_ext_x_key = _mlb_process_ext_x_key;
										tmp_event->hls->cb_post_fetch = _mlb_post_fetch;
										tmp_event->hls->cb_bad_decrypt = _mlb_bad_decrypt;

										if (_key)
										{
											char * _tmp_key = strdup(_key);
											if (_tmp_key)
											{
												b64decode(_tmp_key);
												for (i = 0; i < AES128_KEY_SIZE; i++)
													tmp_event->aes_key[i] = (uint8_t)_tmp_key[i];

												free(_tmp_key);
											}

											if (_param)
											{
												tmp_event->hls->url_params = strdup(_param);
											}
											else
												printf("NO2\n");
										}
										else
										{
											printf("NO1\n");
										}

									}
									tuner->tuned_channel = chan;
									hls_print_stream_info(tmp_event->hls);
								}
							}
							free(tmpstr);
						}
						else
						{
							printf("NO B64 URL\n");
						}
						hls_set_bitrate(tmp_event->hls, mlbdev->start_bitrate);
						hls_update_stream_playlist(tmp_event->hls);
						hls_set_adaptive(tmp_event->hls, mlbdev->adaptive);
						hls_set_min_bitrate(tmp_event->hls, mlbdev->min_bitrate);
						hls_set_max_bitrate(tmp_event->hls, mlbdev->max_bitrate);
						mlbdev->last_auth_time = (uint64_t)time(NULL);

						memcpy(mlbdev->live_event_struct, tmp_event, sizeof(TUNER_MLB_EVENTID_STRUCT));


						if (file_exists(_FORCE_FILE_START_TIME) == 0)
						{
							int filehandle = -1, bytes_read;
							off_t _sz = get_file_size(_FORCE_FILE_START_TIME);

							filehandle = open(_FORCE_FILE_START_TIME, O_RDONLY);
							if (filehandle)
							{
								uint8_t *_mem = calloc(1, _sz);
								if (_mem)
								{
									lseek(filehandle, 0, SEEK_SET);
									bytes_read = get_bytes_from_file(filehandle, _mem, _sz);
									if (bytes_read == _sz)
									{
										mlbdev->game_starttime = strtol((char*)_mem, (char **)NULL, 10);
										printf("[MLB] force_start: %s (%" PRId64")\n", (char*)_mem, mlbdev->game_starttime);
									}
									else
									{
										printf("[MLB] force_start bad!\n");
									}
									free(_mem);
								}
								close(filehandle);
							}
						}

						if (mlbdev->game_starttime)
						{
							uint8_t inn_num, top;
							inn_num = mlbdev->game_starttime / 10;
							top =  !(!(mlbdev->game_starttime % 10));

							if (!inn_num && top)
							{
								segment = hls_set_segment_time(tmp_event->hls->active_stream, game->innings.stream_start_time);
								printf("[MLB] Set start time to beginning of stream (not game): %" PRId64"\n", game->innings.stream_start_time);
							}
							else
							{

								int j;
								for(j =1; j <= 9; j++)
									printf("[%d] M_Top: %" PRId64", M_Bot: %" PRId64"\n", j, game->innings.innings[j].top_start_time, game->innings.innings[j].bottom_start_time);

								if (top)
									stime = game->innings.innings[inn_num].top_start_time;
								else
									stime = game->innings.innings[inn_num].bottom_start_time;

								if (stime == 0)
								{
									printf("[MLB] Want to set inning but time is zero, set to game starttime: %" PRId64"\n", mlbdev->game_starttime);
									segment = hls_set_segment_time(tmp_event->hls->active_stream, mlbdev->game_starttime);
								}
								else
								{
									printf("[MLB] Set start to inning: %d, top: %d (stime: %0.2f)\n", inn_num, top, stime);
									segment = hls_set_segment_time(tmp_event->hls->active_stream, stime);
								}
							}
							// Set start time to beginning of game
						}
						else
						{
							if (hls_get_stream_state(tmp_event->hls->active_stream) == HLS_STATE_LIVE)
							{
								segment = hls_set_segment_live(tmp_event->hls->active_stream);
								printf("[MLB] Set start to LIVE STREAM: %" PRId64"\n", segment->index);
							}
							else
							{
								segment = hls_set_segment_time(tmp_event->hls->active_stream, game->innings.start_time);
								printf("[MLB] Set start time to beginning of game: %" PRId64"\n", game->innings.start_time);
							}
						}


						if (segment)
							printf("\tSEGMENT, index: %" PRId64" -- TC: %0.2f\n", segment->index, segment->timecode);

						ret = 0;
					}
				}
				else
				{
					printf("Event not in list\n");
				}
			}
			else
				printf("Could't refresh game list\n");
		}
//		mlbdev->tuning = 0;
	}
	return ret;
}

uint8_t mlb_close_data(TUNER_STRUCT * tuner)
{
	uint8_t ret = 1;
	if (tuner)
	{
		int i;
		for (i = 0; i < TUNER_MAX_DVB_HANDLES; i++)
		{
			if (tuner->devhandles[i] > 0)
			{
//				printf("Closing sockets\n");
				close(tuner->devhandles[i]);
			}
			tuner->devhandles[i] = -1;
		}
//		printf("[MLB] Stopping & Flushing tuner: %s\n", tuner->name);
	}
	return ret;
}

//uint8_t cheat = 0;

/*
uint64_t _mlb_get_last_pts(HLS_MASTER_URL * master)
{
	uint64_t ret = 0;
	if (master && master->active_stream && master->active_stream->active_segment)
	{
		return floor(master->active_stream->active_segment->timecode);
	}
	return ret;
}
*/
uint64_t _mlb_get_first_pts(uint8_t *data, uint32_t data_sz)
{
	uint64_t ret = 0;
	if (data && data_sz)
	{
		uint32_t i;
		for(i=0; i < data_sz; i+= TS_PACKET_SIZE)
		{
			if (data[i] == TS_SYNC_BYTE)
			{
				BITSTREAM *bitstream = bits_create(&data[i], data_sz);
				if (bitstream)
				{
					MPEGTS_PACKET * packet = mpegts_parse_packet2(bitstream, NULL);
					if (packet)
					{
						if (packet->payload_start_struct)
						{
							if ((packet->payload_start_struct->stream_id >= MPEGTS_VIDEO_STREAM_START && packet->payload_start_struct->stream_id <= MPEGTS_VIDEO_STREAM_END) || 
								(packet->payload_start_struct->stream_id >= MPEGTS_AUDIO_STREAM_START && packet->payload_start_struct->stream_id <= MPEGTS_AUDIO_STREAM_END))
							{
								if (packet->payload_start_struct->pts)
									ret = packet->payload_start_struct->pts;
								else
									ret = packet->payload_start_struct->dts;

								i = data_sz;
							}
						}
						mpegts_free_packet(packet);
					}
					free(bitstream);
				}
			}
		}
	}

	return ret;
}


uint8_t mlb_read_data(TUNER_STRUCT * tuner)
{
	uint8_t ret = DEVICE_READ_ERROR;
	size_t bytes_read = 0;
	size_t read_sz = tuner->read_size;

//	printf("mlb_read_data: %" PRId64"\n", tuner->read_size);
	if (tuner && tuner->parent)
	{
		DEVICE_STRUCT *dev = (DEVICE_STRUCT *)tuner->parent;
		TUNER_CHANNEL_STRUCT * channel = device_channel_get_struct(dev, tuner->tuned_channel, tuner->tuned_channel);

		if (channel && dev->parent)
		{
			TUNER_MLB_STRUCT *mlbdev = (TUNER_MLB_STRUCT *)dev->parent;
			if (mlbdev)
			{
//				if (mlbdev->read_overflow && mlbdev->read_overflow_pos && mlbdev->read_overflow_pos < tuner->read_size)
				pthread_mutex_lock(&mlbdev->seek_mutex);

				if (mlbdev->read_overflow && mlbdev->read_overflow_read_pos < mlbdev->read_overflow_fill_pos)
				{
					if (mlbdev->read_overflow_fill_pos - mlbdev->read_overflow_read_pos >= read_sz)
					{
						memcpy(tuner->read_data_ptr, mlbdev->read_overflow + mlbdev->read_overflow_read_pos, read_sz);
						mlbdev->read_overflow_read_pos += read_sz;
						pthread_mutex_unlock(&mlbdev->seek_mutex);

						return DEVICE_READ_NOERROR;
					}
					else
					{
						bytes_read = mlbdev->read_overflow_fill_pos - mlbdev->read_overflow_read_pos;
						memcpy(tuner->read_data_ptr, mlbdev->read_overflow + mlbdev->read_overflow_read_pos, bytes_read);
						read_sz -= bytes_read;
					}
				}
				else
				{
//					printf("Z1\n");
//					printf("FME!!\n");
				}

				TUNER_MLB_EVENTID_STRUCT *tmp_event = mlbdev->live_event_struct;
				if (tmp_event)
				{
					if (tmp_event->hls)
					{
						HLS_MASTER_URL * master = tmp_event->hls;
						if (master->active_stream)
						{
							HLS_STREAM_URL *stream = master->active_stream;
							if (stream)
							{
								HLS_SEGMENT *segment = NULL;
								master->fetch_proxy = mlbdev->fetch_proxy;
								master->fetch_proxy_type = mlbdev->fetch_proxy_type;

								while(read_sz)
								{
									if (!stream->segment_count)
									{
										usleep(250*1000);
										continue;
									}
									segment = hls_stream_fetch_decrypt(stream);

									if (segment)
									{
//										printf("segment: %" PRId64"  -- %0.2f\n", segment->index, segment->timecode);

										if (mlbdev->need_sync_tc && segment->timecode > mlbdev->need_sync_tc)
										{
											printf("need to sync after seek: %0.2f -- %0.2f\n", mlbdev->need_sync_tc, segment->timecode);
//											segment = segment->next;
											continue;
										}

										mlbdev->need_sync_tc = 0;
										printf("[MLB] hls_stream_fetch_decrypt [%" PRId64"] TC:  %0.2f, Time: %" PRId64" (ms) (%0.2f)\n", segment->index, segment->timecode, segment->process_time, segment->duration);

										if (segment->process_time / 1000.0 >  segment->duration)
											mlbdev->fetch_lag_count++;
										else
											mlbdev->fetch_lag_count = 0;

										if (mlbdev->fetch_lag_count > 2)
										{
//											printf("[MLB] Cyclying CDN, before: %s\n", master->base_url);
											_mlb_switch_cdn(master->base_url,  MLB_CDN_OVERRIDE_CYCLE);
//											printf("[MLB] Cyclying CDN, after: %s\n", master->base_url);
											mlbdev->fetch_lag_count = 0;
										}

										if (!mlbdev->playlist_stream_offset)
										{
											mlbdev->playlist_time = segment->timecode;
											mlbdev->pts_time = _mlb_get_first_pts(master->decrypt_mem, master->decrypt_mem_ret);
											mlbdev->playlist_stream_offset = mlbdev->playlist_time  - ((double)mlbdev->pts_time/90000.0);
											printf("[MLB] HLS Playlist Time: %0.2f, MPEG-TS Time (PTS, 90k): %" PRId64" -- diff: %0.2f\n",  mlbdev->playlist_time , mlbdev->pts_time, mlbdev->playlist_stream_offset);
										}

										if (stream)
										{
											if (mlbdev->last_stream_index == 0xFF)
											{
												mlbdev->last_stream_index = stream->index;
											}
											else
											{
												if (mlbdev->last_stream_index != stream->index)
												{
//													tuner->send_stream_change = 1;
													printf("[MLB] Switched streams!! need to inform clients downstream: %d -> %d (%d)\n", mlbdev->last_stream_index , stream->index, tuner->valid_count);
													mlbdev->last_stream_index = stream->index;
													if (read_sz - TS_PACKET_SIZE > 0)
													{
														memcpy(tuner->read_data_ptr + bytes_read, SPECIAL_NULL_PACKET_STREAM_CHANGED, TS_PACKET_SIZE);
														read_sz -= TS_PACKET_SIZE;
														bytes_read += TS_PACKET_SIZE;
													}
													else
													{
														printf("-------- @@@@@@@@ something funky, readsz: %" PRId64"\n", read_sz);
													}
													device_tuner_clear_streams(tuner);
													tuner->reset_valid_pids = 1;
//													tuner->reset_video_pids = 1;
//													tuner->reset_audio_pids = 1;

												}
											}
										}
									}
									else
									{
										printf("[MLB] mlb_read_data, segment is null: %" PRId64"\n", stream->segment_count);
/*
										while(1)
										{
											usleep(1000*1000);
											printf("Sleep 1\n");
										}
										pthread_mutex_unlock(&mlbdev->seek_mutex);
*/
										pthread_mutex_unlock(&mlbdev->seek_mutex);
										return DEVICE_READ_NODATA;
									}

									if (master->decrypt_mem_ret > read_sz)
									{
										memcpy(tuner->read_data_ptr + bytes_read, master->decrypt_mem, read_sz);
										mlbdev->read_overflow_fill_pos = master->decrypt_mem_ret - read_sz;
										memcpy(mlbdev->read_overflow, master->decrypt_mem + read_sz, mlbdev->read_overflow_fill_pos);
										mlbdev->read_overflow_read_pos = 0;
										bytes_read += read_sz;
										read_sz = 0;
										ret = DEVICE_READ_NOERROR;
									}
									else
									{
										memcpy(tuner->read_data_ptr + bytes_read, master->decrypt_mem, master->decrypt_mem_ret);
										read_sz -= master->decrypt_mem_ret;
										bytes_read += master->decrypt_mem_ret;
									}

								}
								segment = master->active_stream->first_segment;
							}
							else
							{
								printf("Z4\n");
							}

						}
						else
						{
							printf("Z3\n");
						}

					}
					else
					{
						printf("Z2: %s\n", channel->info);
					}
				}
				else
				{
					printf("B3\n");
				}
				pthread_mutex_unlock(&mlbdev->seek_mutex);
			}
			else
			{
				printf("B2\n");
			}
		}
		else
		{
			printf("B1\n");
		}

	}
	else
	{
		printf("B0\n");
	}
	return ret;
}

int _mlb_channel_change(TUNER_STRUCT *tuner, TUNER_CHANNEL_STRUCT *chan_struct)
{
//	printf("_mlb_channel_change start\n");
//	printf("_mlb_channel_change end\n");
	return 0;
}

int __mlb_getconfig(TUNER_MLB_STRUCT *dev)
{
	int filehandle = -1;
	char *tmp_mem = NULL;
	off_t sz = 0;

	if (!dev)
	{
//	 	printf("[MLB] __mlb_getconfig: device is null\n");
		return 1;
	}

	filehandle = open(dev->config_file, O_RDONLY);

	if (filehandle == -1)
	{
//	 	printf("[MLB] Can't open ATSC channel config\n");
		return 1;
	}

	sz = get_file_size(dev->config_file);
	tmp_mem = calloc(1, sz);

	if (tmp_mem)
	{
		int bytes_read = 0;
		TEXTFILE_IN_MEMORY *memf = NULL;
		char * line = NULL;

		lseek(filehandle, 0, SEEK_SET);
		bytes_read = get_bytes_from_file(filehandle, (uint8_t *)tmp_mem, sz);

		if (bytes_read != sz)
		{
//			printf("READ ERRORORORORO: %d\n", bytes_read);
			return 0;
		}
		close(filehandle);
		memf = memfile_init((char*)tmp_mem, (int)bytes_read);
		if (memf)
		{
			line = memfile_getnext_line(memf, 1);
			if (line)
			{
				dev->login = strdup(line);
				line = memfile_getnext_line(memf, 1);
				if (line)
				{
					int i;
					dev->password = strdup(line);
/*
					line = memfile_getnext_line(memf, 1);
					if (line)
						dev->cookiefile = strdup(line);
					else
						dev->cookiefile = strdup(MLB_DEFAULT_COOKIEFILE);
*/
					printf("[MLB] Settings/Opts:\n");
					printf("\tUsername/Pass file: %s\n",dev->config_file);
					printf("\tTeam Map file: %s\n", dev->channels_file);

					printf("\tEmail: %s\n", dev->login);
					printf("\tPass: %c", dev->password[0]);
					for(i=1; i < strlen(dev->password)-1; i++)
						printf("*");
					printf("%c\n", dev->password[i]);
					printf("\tCookiefile: %s\n", dev->cookiefile);
					printf("\tSession Key file: %s\n", dev->skey_file);
				}
			}

			free(memf);
		}

		free(tmp_mem);
	}
	return 0;
}


uint8_t mlb_destroy(TUNER_STRUCT * tuner)
{
	TUNER_STRUCT * del = NULL;
	DEVICE_STRUCT *device = NULL;

	if (tuner->parent)
		device = (DEVICE_STRUCT *)tuner->parent;
	printf("[MLB] Exiting, cleaning up...\n");

	while(tuner)
	{
		device_tuner_destroy(tuner);
		del = tuner;
		tuner = tuner->next;

		if (del)
		{
			mpegts_io_close(del->mpegts_io, 1);
			//free(del);
		}
	}

	if (device)
		device_destroy(device);
	return 0;
}

TUNER_MLB_STRUCT * tuner_mlb_init(DEVICE_INIT_ARGS_STRUCT *devopts)
{
	TUNER_MLB_STRUCT * mlbdev = NULL;

	if (devopts && devopts->device_name && devopts->config_file && devopts->bind_ip && devopts->bind_port)
	{
		printf("[MLB] Listening on: %s:%d\n", devopts->bind_ip, devopts->bind_port);
		mlbdev = calloc(1, sizeof(TUNER_MLB_STRUCT));
		if (mlbdev)
		{
			mlbdev->channel_num_offset = devopts->channel_num_offset;
			if (devopts->config_file)
				mlbdev->config_file = strdup(devopts->config_file);

			if (devopts->channels_file)
				mlbdev->channels_file = strdup(devopts->channels_file);

			if (devopts->session_key_file)
				mlbdev->skey_file = strdup(devopts->session_key_file);

			if (devopts->timer_file)
				mlbdev->cookiefile = strdup(devopts->timer_file);

			mlbdev->device = device_init(devopts->bind_ip, devopts->bind_port);
			mlbdev->read_overflow_sz = MLB_OVERFLOW_SZ;
			mlbdev->read_overflow = malloc(mlbdev->read_overflow_sz);
			mlbdev->read_overflow_fill_pos = 0;
			mlbdev->read_overflow_read_pos = 0;
			mlbdev->epg_days = 14;
			mlbdev->last_stream_index = 0xFF;

			mlbdev->connect_timeout = 5;
			mlbdev->fetch_timeout = 0;

			if (mlbdev->device)
			{
				int i;
				char tmpdev[MAX_DEVNAME_SIZE] = {0};
				TUNER_STRUCT *tmptuner = NULL;

				__mlb_getchannel_lineup(mlbdev);
//				device_channel_print_all(mlbdev->device);

				if (!mlbdev->curl_handle)
				{
					curl_global_init(CURL_GLOBAL_ALL);
					mlbdev->curl_handle = curl_easy_init();
				}

				mlbdev->device->stream_only = 1;
				__mlb_getconfig(mlbdev);
/*
				if (strlen(proxy_addr) > 5)
				{
					curl_easy_setopt(mlbdev->curl_handle, CURLOPT_PROXY, proxy_addr);
//					printf("Setting proxy to: %s\n", proxy_addr);
					curl_easy_setopt(mlbdev->curl_handle, CURLOPT_PROXY, proxy_addr);
					if (proxy_addr[0] == 's' && proxy_addr[1] == 'o' && proxy_addr[2] == 'c' && proxy_addr[3] == 'k')
					{
//						printf("Proxy type: SOCKS\n");
						curl_easy_setopt(mlbdev->curl_handle, CURLOPT_PROXYTYPE, CURLPROXY_SOCKS5);
					}
					else
					{
//						printf("Proxy type: HTTP\n");
						curl_easy_setopt(mlbdev->curl_handle, CURLOPT_PROXYTYPE, CURLPROXY_HTTP);
					}
				}
				//	curl_easy_setopt(mlbdev->curl_handle, CURLOPT_VERBOSE, 1);
*/



				mlbdev->device->cb_dev_printf = generic_vprintw;
				mlbdev->device->max_tuners = devopts->max_tuners;
//				mlbdev->device->cb_gui_incoming_parse = _gui_incoming_parse;

				pthread_mutex_lock(&mlbdev->device->tunerlock);
				mlbdev->device->parent = (void *) mlbdev;

				mlbdev->device->cb_epg_chan = __mlb_getchan_epg;

				for (i=0; i < mlbdev->device->max_tuners; i++)
				{
					tmptuner = device_tuner_init(TUNE_TYPE_HLS);
					if (tmptuner)
					{
						int q;

						for (q=0; q < TUNER_MAX_DVB_HANDLES; q++)
							tmptuner->devhandles[q] = -1;

						tmptuner->parent = (void*) mlbdev->device;

						snprintf(tmpdev, MAX_DEVNAME_SIZE, "MLB.tv Tuner-%02d", i);
						strncpy(tmptuner->name, tmpdev, strlen(tmpdev));

						tmptuner->cb_gui_tuner_read_status  = win_tuner_read_status;
						tmptuner->cb_gui_tuner_write_status  = win_tuner_write_status;

						tmptuner->id_offset =  1;
						tmptuner->id = i+1;

						tmptuner->segment_dir = devopts->segment_dir;
						tmptuner->base_filename = devopts->base_filename;

						tmptuner->mpegts_fileunit_maxsize = devopts->max_segment_size;
						tmptuner->mpegts_fileunit_maxlife = devopts->max_segment_life;

						tmptuner->read_thread_sleep_time = 64*1000;

						tmptuner->cb_read_data = mlb_read_data;
						tmptuner->cb_close_data = mlb_close_data;
						tmptuner->cb_tune_channel = mlb_tune_channel;

						tmptuner->packets_to_buffer = 10;
						tmptuner->read_size = MLB_DEFAULT_READ_SZ;
//						tmptuner->read_size = 0;

						tmptuner->cb_gui_tuner_read_status  = win_tuner_read_status;
						tmptuner->cb_gui_tuner_write_status  = win_tuner_write_status;

		
//						tmptuner->io_opts.cb_seek_to_time = _mlb_seek_to_time;
//						tmptuner->io_opts.cb_get_current_time = _mlb_get_current_time;
//						tmptuner->io_opts.cb_get_start_time = _mlb_get_start_time;
//						tmptuner->io_opts.cb_get_end_time = _mlb_get_end_time;
						tmptuner->io_opts.extra_data2 = (void*)tmptuner;


						device_tuner_add(mlbdev->device, tmptuner);
/*
						device_channel_add(mlbdev->device, tmptuner->id, tmptuner->id);
						snprintf(tmpdev, MAX_DEVNAME_SIZE, "MLB.tv Channel: %02d", i);
						device_channel_add_name(mlbdev->device, tmptuner->id, tmptuner->id, tmpdev);
*/
//						snprintf(tmpdev, MAX_DEVNAME_SIZE, "mlb_epg_id_%02d", i);
//						device_channel_add_xmltv_id(mlbdev->device, tmptuner->id, tmptuner->id, tmpdev);
					}

				}

				if (devopts->segment_dir)
				{
					char *_tmp = url_encode(devopts->segment_dir);
//					strncpy(mlbdev->auth.session_key, devopts->segment_dir, sizeof(mlbdev->auth.session_key)-1);
					if (_tmp)
					{
						//printf("huh: %s\n", _tmp);
						strncpy(mlbdev->auth.session_key, _tmp, strlen(_tmp));
						free(_tmp);
						printf("[MLB] Setting Session key from Arg: %s\n", mlbdev->auth.session_key);
					}
				}
				else
				{
					__mlb_get_skey(mlbdev);
				}

				pthread_mutex_unlock(&mlbdev->device->tunerlock);
			}
			else
			{
//				printf("I iz sad\n");
				pthread_mutex_unlock(&mlbdev->init_mutex);
			}
		}
	}
	return mlbdev;
#
}

void _mlb_expire_epg_cache(TUNER_MLB_STRUCT *mlbdev, uint32_t expire_time)
{
	if (mlbdev)
	{
		int i;
		uint64_t now = time(NULL);

		// Expire old cache items here
		for(i=0; i < TUNER_MLB_MAX_EPG_CACHE_DAYS; i++)
		{
			if (mlbdev->epg_cache[i].time && now - mlbdev->epg_cache[i].time > expire_time)
			{
				if (mlbdev->epg_cache[i].epg)
				{
					if (mlbdev->epg_cache[i].epg->data)
						free(mlbdev->epg_cache[i].epg->data);
					free(mlbdev->epg_cache[i].epg);
				}

				if (mlbdev->epg_cache[i].grid)
				{
					if (mlbdev->epg_cache[i].grid->data)
						free(mlbdev->epg_cache[i].grid->data);
					free(mlbdev->epg_cache[i].grid);
				}
				memset(&mlbdev->epg_cache[i], 0, sizeof(mlbdev->epg_cache[i]));
			}
		}
	}
}

TUNER_MLB_EPG_XML_CACHE *_mlb_add_epg_cache(TUNER_MLB_STRUCT *mlbdev, int month, int day, int year, UTILS_DATAPAIR_STRUCT *epg, UTILS_DATAPAIR_STRUCT * grid)
{
	TUNER_MLB_EPG_XML_CACHE * ret = NULL;
	if (mlbdev && month && day && year)
	{
		int i;

		_mlb_expire_epg_cache(mlbdev, TUNER_MLB_MAX_EPG_CACHE_EXPIRE);

		for(i=0; i < TUNER_MLB_MAX_EPG_CACHE_DAYS; i++)
		{
			if(mlbdev->epg_cache[i].time == 0)
			{
				mlbdev->epg_cache[i].time = (uint64_t)time(NULL);
				mlbdev->epg_cache[i].month = month;
				mlbdev->epg_cache[i].day = day;
				mlbdev->epg_cache[i].year = year;
				mlbdev->epg_cache[i].epg = epg;
				mlbdev->epg_cache[i].grid = grid;
				return &mlbdev->epg_cache[i];
			}
		}
	}
	return ret;
}

TUNER_MLB_EPG_XML_CACHE *_mlb_get_epg_cache(TUNER_MLB_STRUCT * mlbdev, int month, int day, int year)
{
	TUNER_MLB_EPG_XML_CACHE * ret = NULL;
	if (mlbdev && month && day && year)
	{
		int i;

		_mlb_expire_epg_cache(mlbdev, TUNER_MLB_MAX_EPG_CACHE_EXPIRE);

		for(i=0; i < TUNER_MLB_MAX_EPG_CACHE_DAYS; i++)
		{
			if (mlbdev->epg_cache[i].month == month &&
				mlbdev->epg_cache[i].day == day &&
				mlbdev->epg_cache[i].year == year)
			{
				if (mlbdev->epg_cache[i].epg && mlbdev->epg_cache[i].grid)
				{
					return &mlbdev->epg_cache[i];
				}
			}
		}
	}
	return ret;
}


int __mlb_getload_games(TUNER_MLB_STRUCT * mlbdev, uint64_t fetch_date, uint8_t restore)
{
	int ret = 0;
	if (mlbdev)
	{
		TUNER_MLB_EPG_XML_CACHE *_ecache = NULL;
		TUNER_MLB_EVENTID_STRUCT * tmp_event = NULL;
		uint8_t fetch_url[MAX_FILENAME_SIZE] = {0};
		struct tm * timeinfo = NULL;
		char error_buf[CURL_ERROR_SIZE] = {0};
		UTILS_DATAPAIR_STRUCT *carg_epg = NULL, *carg_grid = NULL;
		HLS_FETCH_OPTIONS _fetch_opts = {0};
		uint32_t count = 0;
		int year=0, month=0, day=0;

		_fetch_opts.errbuf = error_buf;

		pthread_mutex_lock(&mlbdev->curl_mutex);

		if (!fetch_date)
			fetch_date = (uint64_t)time(NULL);

		timeinfo = localtime ((time_t*)&fetch_date);

		year = timeinfo->tm_year + 1900;
		month = timeinfo->tm_mon + 1;
		day = timeinfo->tm_mday;

		_ecache = _mlb_get_epg_cache(mlbdev, month, day, year);
		if (_ecache)
		{
//			printf("HI?: %d %d %d\n", month, day, year);
			carg_epg = _ecache->epg;
			carg_grid = _ecache->grid;
		}

		if (carg_epg == NULL || carg_grid == NULL)
		{
			snprintf((char*)fetch_url, MAX_FILENAME_SIZE-1, MLB_EPG_XML, year, month, day);
//			printf("fetch_url (e): %s\n", fetch_url);

			while(!carg_epg)
			{
//				printf("fetch_url: %s (%d)\n", fetch_url, count);
				carg_epg = hls_curl_get((char*)fetch_url, &_fetch_opts, NULL);
				if (!carg_epg)
				{
					printf("[MLB] EPG,retry fetch_url1: %s (%d)\n", fetch_url, count);
					usleep(1000*1000);
				}
				count++;
				if (count > 10)
				{
					printf("[MLB] Couldn't get EPG, exiting\n");
					return -1;
				}
			}
			count = 0;
//			printf("epg1: %s (%d)\n", fetch_url, carg_epg->data_size);

			snprintf((char*)fetch_url, MAX_FILENAME_SIZE-1, MLB_GRID_XML, year, month, day);
//			printf("fetch_url (g): %s\n", fetch_url);
			while(!carg_grid)
			{
//				printf("fetch_url: %s (%d)\n", fetch_url, count);
				carg_grid = hls_curl_get((char*)fetch_url, &_fetch_opts, NULL);
				if (!carg_grid)
				{
					printf("[MLB] EPG,retry fetch_url2: %s (%d)\n", fetch_url, count);
					usleep(1000*1000);
				}
				count++;
				if (count > 10)
				{
					printf("[MLB] Couldn't get GRID, exiting\n");
					return -1;
				}
			}
//			printf("epg2: %s (%d)\n", fetch_url, carg_grid->data_size);

			if (carg_epg && carg_grid)
			{
				_mlb_add_epg_cache(mlbdev, month, day, year, carg_epg, carg_grid);
			}
		}
		else
		{
//			printf("epg, using cache: %d %d %d\n", month, day, year);
		}

		if (carg_epg && carg_grid && carg_epg->data && carg_grid->data)
		{
			uint8_t do_refresh = 1;
			xmlChar * tmpchar = NULL;
			xmlXPathObjectPtr p = NULL;
		    xmlNodePtr tmp_node = NULL;
			xmlChar tmpstr[MLB_MAX_XML_LEN] = {0};

			xmlDocPtr epg_xml = xmlReadMemory((char*)carg_epg->data, carg_epg->data_size, NULL, NULL, 0);
			if (epg_xml)
			{
				tmp_node = epg_xml->children;

				while (tmp_node)
				{
//					printf("HMM: %s\n", tmp_node->name);
					if (xmlStrcmp(tmp_node->name, (xmlChar*)MLB_XML_EPG) == 0)
					{
						tmpchar = xmlGetProp(tmp_node, (xmlChar *)MLB_XML_EPG_LASTMOD);
						if (tmpchar)
						{

							uint64_t ltime = utils_str_to_epoch2((char*)tmpchar, 1);
/*
							strptime(tmpchar, "%FT%T%z", &tm);
							tm.tm_isdst = -1;
							ltime = (uint64_t) timegm(&tm);
*/
							if (ltime != mlbdev->games_last_mod_time)
							{
								mlbdev->games_last_mod_time = ltime;
//								printf("refreshing: %s\n", utils_epoch_to_str(ltime));
								do_refresh = 1;
							}

//							printf("last_modified: %s\n", (char*) tmpchar);
							xmlFree(tmpchar);
							break;
						}
					}
					tmp_node = tmp_node->next;
				}

				if (do_refresh)
				{
					int i;
					mlbdev->games_count = 0;
					for(i=0; i <  TUNER_MLB_MAX_GAMES; i++)
						memset(&mlbdev->games[i], 0, sizeof(TUNER_MLB_GAME_STRUCT));

					snprintf((char*)tmpstr, MLB_MAX_XML_LEN-1, MLB_XPATH_EPG_GAMES);
					p = xmltv_getnodeset (epg_xml, tmpstr);
					if (p)
					{
						for(i=0; i < p->nodesetval->nodeNr; i++)
						{
							tmp_node = p->nodesetval->nodeTab[i];
							if (tmp_node)
							{
								tmpchar = xmlGetProp(tmp_node, (xmlChar *)MLB_XML_EPG_CAL_EVENTID);
								if (tmpchar)
								{
									strncpy(mlbdev->games[mlbdev->games_count].calendar_event_id, (char*)tmpchar, sizeof(mlbdev->games[mlbdev->games_count].calendar_event_id) - 1);
//									printf("ID: %s\n", mlbdev->games[mlbdev->games_count].calendar_event_id);
									xmlFree(tmpchar);
								}

								tmpchar = xmlGetProp(tmp_node, (xmlChar *)MLB_XML_EPG_VENUE);
								if (tmpchar)
								{
									strncpy(mlbdev->games[mlbdev->games_count].venue, (char*)tmpchar, sizeof(mlbdev->games[mlbdev->games_count].venue) - 1);
//									printf("Venue: %s\n", mlbdev->games[mlbdev->games_count].venue);
									xmlFree(tmpchar);
								}

								tmpchar = xmlGetProp(tmp_node, (xmlChar *)MLB_XML_EPG_START);
								if (tmpchar)
								{
//									strptime(tmpchar, "%FT%T%z", &tm);
//									tm.tm_isdst = -1;
//									mlbdev->games[mlbdev->games_count].start_epoch = (uint64_t) mktime(&tm);
									mlbdev->games[mlbdev->games_count].start_epoch = (uint64_t) utils_str_to_epoch2((char*)tmpchar, 0);
//									printf("Start: %s\n", tmpchar);

									xmlFree(tmpchar);
								}

								tmpchar = xmlGetProp(tmp_node, (xmlChar *)MLB_XML_EPG_AW_TEAM_CITY);
								if (tmpchar)
								{
									strncpy(mlbdev->games[mlbdev->games_count].away_team_city, (char*)tmpchar, sizeof(mlbdev->games[mlbdev->games_count].away_team_city) - 1);
									xmlFree(tmpchar);
								}

								tmpchar = xmlGetProp(tmp_node, (xmlChar *)MLB_XML_EPG_AW_CODE);
								if (tmpchar)
								{
									strncpy(mlbdev->games[mlbdev->games_count].away_team_code, (char*)tmpchar, sizeof(mlbdev->games[mlbdev->games_count].away_team_code) - 1);
									xmlFree(tmpchar);
								}

								tmpchar = xmlGetProp(tmp_node, (xmlChar *)MLB_XML_EPG_AW_FILE_CODE);
								if (tmpchar)
								{
									strncpy(mlbdev->games[mlbdev->games_count].away_team_file_code, (char*)tmpchar, sizeof(mlbdev->games[mlbdev->games_count].away_team_file_code) - 1);
									xmlFree(tmpchar);
								}

								tmpchar = xmlGetProp(tmp_node, (xmlChar *)MLB_XML_EPG_AW_TEAM_NAME);
								if (tmpchar)
								{
									strncpy(mlbdev->games[mlbdev->games_count].away_team_name, (char*)tmpchar, sizeof(mlbdev->games[mlbdev->games_count].away_team_name) - 1);
									xmlFree(tmpchar);
								}

								tmpchar = xmlGetProp(tmp_node, (xmlChar *)MLB_XML_EPG_HM_CODE);
								if (tmpchar)
								{
									strncpy(mlbdev->games[mlbdev->games_count].home_team_code, (char*)tmpchar, sizeof(mlbdev->games[mlbdev->games_count].home_team_code) - 1);
									xmlFree(tmpchar);
								}

								tmpchar = xmlGetProp(tmp_node, (xmlChar *)MLB_XML_EPG_HM_FILE_CODE);
								if (tmpchar)
								{
									strncpy(mlbdev->games[mlbdev->games_count].home_team_file_code, (char*)tmpchar, sizeof(mlbdev->games[mlbdev->games_count].home_team_file_code) - 1);
									xmlFree(tmpchar);
								}

								tmpchar = xmlGetProp(tmp_node, (xmlChar *)MLB_XML_EPG_HM_TEAM_CITY);
								if (tmpchar)
								{
									strncpy(mlbdev->games[mlbdev->games_count].home_team_city, (char*)tmpchar, sizeof(mlbdev->games[mlbdev->games_count].home_team_city) - 1);
									xmlFree(tmpchar);
								}

								tmpchar = xmlGetProp(tmp_node, (xmlChar *)MLB_XML_EPG_HM_TEAM_NAME);
								if (tmpchar)
								{
									strncpy(mlbdev->games[mlbdev->games_count].home_team_name, (char*)tmpchar, sizeof(mlbdev->games[mlbdev->games_count].home_team_name) - 1);
									xmlFree(tmpchar);
								}
								tmpchar = xmlGetProp(tmp_node, (xmlChar *)MLB_XML_EPG_DATA_DIR);
								if (tmpchar)
								{
									strncpy(mlbdev->games[mlbdev->games_count].game_data_directory, (char*)tmpchar, sizeof(mlbdev->games[mlbdev->games_count].game_data_directory) - 1);
									xmlFree(tmpchar);
								}
							}
							mlbdev->games_count++;
						}
						xmlXPathFreeObject(p);
					}
					else
					{
//						printf("xpath bad1: %s\n", tmpstr);
//						printf("xpath bad2: %s\n", carg_epg->data);
						ret = -2;
					}
				}
/*
				else
				{
//					printf("Not refreshing\n");
				}
*/
				xmlFreeDoc(epg_xml);
			}
			else
			{
				printf("xmlReadMemory bad1\n");
				ret = -2;

			}

			if (ret == 0)
			{
				epg_xml = xmlReadMemory((char*)carg_grid->data, carg_grid->data_size, NULL, NULL, 0);
				if (epg_xml)
				{
					int j, i;
//					uint8_t _b64_url[TUNER_MLB_MAX_STR_LEN];
					for(i=0; i < TUNER_MLB_MAX_GAMES; i++)
					{
						mlbdev->games[i].events_count = 0;
						mlbdev->games_total_count = 0;

						for(j=0; j < TUNER_MLB_MAX_EVENTS; j++)
						{
//							memcpy(_b64_url, &mlbdev->games[i].events[j].b64_url, TUNER_MLB_MAX_STR_LEN);
							memset(&mlbdev->games[i].events[j], 0, sizeof(TUNER_MLB_EVENTID_STRUCT));
//							memcpy(&mlbdev->games[i].events[j].b64_url, _b64_url, TUNER_MLB_MAX_STR_LEN);
						}
/*
						if (dev)
						{
							printf("Clearing %d\n", i+1);
							device_channel_reset_data(dev, i+1, i+1);
						}
*/
					}

					for(i=0; i < mlbdev->games_count; i++)
					{
						//snprintf((char*)tmpstr, MLB_MAX_XML_LEN-1, MLB_XPATH_GRID_HOMEBASE_MEDIA_TYPE2, mlbdev->games[i].calendar_event_id, MLB_XML_GRID_HOMEBASE_TYPE_AW, MLB_XML_GRID_HOMEBASE_TYPE_HM, MLB_SCENARIO);
						snprintf((char*)tmpstr, MLB_MAX_XML_LEN-1, MLB_XPATH_GRID_HOMEBASE_MEDIA_TYPE3, mlbdev->games[i].calendar_event_id, MLB_XML_GRID_HOMEBASE_TYPE_AW, MLB_XML_GRID_HOMEBASE_TYPE_HM,MLB_XML_GRID_HOMEBASE_TYPE_NATIONAL, MLB_SCENARIO);
//						printf("TMPSTR: %s\n", tmpstr);
						p = xmltv_getnodeset (epg_xml, tmpstr);
						if (p)
						{
							snprintf((char*)tmpstr, MLB_MAX_XML_LEN-1, MLB_XPATH_GRID_GAME, mlbdev->games[i].calendar_event_id);
							xmlXPathObjectPtr q = xmltv_getnodeset (epg_xml, tmpstr);
							if (q)
							{
								tmpchar = xmlGetProp(q->nodesetval->nodeTab[0], (xmlChar *)MLB_XML_GRID_STATUS);
								if (tmpchar)
								{
									strncpy(mlbdev->games[i].status, (char*)tmpchar, sizeof(mlbdev->games[i].status) - 1);
									xmlFree(tmpchar);
								}
								xmlXPathFreeObject(q);
							}

							for(j=0; j < p->nodesetval->nodeNr; j++)
							{
								tmp_event = &mlbdev->games[i].events[mlbdev->games[i].events_count];
								tmpchar = xmlGetProp(p->nodesetval->nodeTab[j], (xmlChar *)MLB_XML_GRID_HOMEBASE_TYPE);
								if (tmpchar)
								{
									strncpy(tmp_event->type, (char*)tmpchar, sizeof(tmp_event->type) - 1);
//									printf("\t\tTYPE: %s\n", tmp_event->type);
									xmlFree(tmpchar);
								}

								tmpchar = xmlGetProp(p->nodesetval->nodeTab[j], (xmlChar *)MLB_XML_GRID_HOMEBASE_DISPLAY);
								if (tmpchar)
								{
									strncpy(tmp_event->display, (char*)tmpchar, sizeof(tmp_event->display) - 1);
//									printf("\t\tDISPLY: %s\n", tmp_event->display);
									xmlFree(tmpchar);
								}

								tmpchar = xmlGetProp(p->nodesetval->nodeTab[j], (xmlChar *)MLB_XML_GRID_HOMEBASE_ID);
								if (tmpchar)
								{
									strncpy(tmp_event->id, (char*)tmpchar, sizeof(tmp_event->id) - 1);
//									printf("\t\tID: %s\n", tmp_event->id);

									xmlFree(tmpchar);
								}

								tmpchar = xmlGetProp(p->nodesetval->nodeTab[j], (xmlChar *)MLB_XML_GRID_HOMEBASE_BLACKOUT);
								if (tmpchar)
								{
									strncpy(tmp_event->blackout, (char*)tmpchar, sizeof(tmp_event->blackout) - 1);
//									printf("\t\tBLACKOUT: %s\n", tmp_event->blackout);
									xmlFree(tmpchar);
								}

								tmpchar = xmlGetProp(p->nodesetval->nodeTab[j], (xmlChar *)MLB_XML_GRID_HOMEBASE_STATE);
								if (tmpchar)
								{
									strncpy(tmp_event->state, (char*)tmpchar, sizeof(tmp_event->state) - 1);
//									printf("\t\tSTATE: %s\n", tmp_event->state);

									xmlFree(tmpchar);
								}

								tmpchar = xmlGetProp(p->nodesetval->nodeTab[j], (xmlChar *)MLB_XML_GRID_HOMEBASE_FREE);
								if (tmpchar)
								{
									strncpy(tmp_event->free, (char*)tmpchar, sizeof(tmp_event->free) - 1);
//									printf("\t\tFREE: %s\n", tmp_event->free);

									xmlFree(tmpchar);
								}

								mlbdev->games[i].events_count++;
								mlbdev->games_total_count++;
								tmp_event->game_number = mlbdev->games_total_count;
								tmp_event->parent = &mlbdev->games[i];
/*
								if (dev)
								{
									device_channel_add_info(dev, mlbdev->games_total_count, mlbdev->games_total_count, 
								}
*/
							}
							xmlXPathFreeObject(p);
						}
					}

					xmlFreeDoc(epg_xml);
				}
				else
				{
					printf("xmlReadMemory bad2\n");
					ret = -1;
				}
			}
			else
			{
//				printf("no epg data: %d\n", ret);
			}
		}
		else
			ret = -1;

		pthread_mutex_unlock(&mlbdev->curl_mutex);
/*
		if (carg_epg->data)
			free(carg_epg->data);

		if (carg_grid->data)
			free(carg_grid->data);
*/
	}
	return ret;
}

int __mlb_getchan_epg(TUNER_INITPASS_STRUCT *tpass)
{
	if (tpass && tpass->dataptr && tpass->device && tpass->device->parent)
	{
		TIMER_STRUCT_EXTRA extra = {0};
		TIMER_STRUCT *first_timer = NULL, *current_timer = NULL, *tmp_timer = NULL, timer_no_game = {{0}};
		TUNER_MLB_EVENTID_STRUCT * tmp_event = NULL;
		TUNER_MLB_STRUCT * mlbdev = (TUNER_MLB_STRUCT *)tpass->device->parent;
		TUNER_CHANNEL_STRUCT * channel = NULL;
		TIMER_STRUCT_SEND *tsend = (TIMER_STRUCT_SEND *)tpass->dataptr;


		uint64_t fetch_date = 0, end_fetch_date = 0;
		uint32_t chan_num = 0, days = mlbdev->epg_days;

		int i, q;

		char *chan_id = (char*)tsend->url;
		char *datestr = NULL;

		for(i=0; i < strlen(chan_id); i++)
		{
			if (chan_id[i] == '_')
			{
				chan_id[i] = '\0';
				break;
			}
		}

		datestr = &chan_id[i+1];
//		printf("BB1: %s (%ld)\n", chan_id, strlen(chan_id));
		q = strlen(datestr);
		for(i=0; i < q; i++)
		{
			if (datestr[i] == '_')
			{
				datestr[i] = '\0';
				break;
			}
		}
//		printf("HMM: %s, %d %ld\n", datestr, i, strlen(datestr));

		if (i < q)
			days = atoi(&datestr[i+1]);

		if (days == 0)
			days = 1;

		chan_num = atoi(chan_id);

		fetch_date = strtol(datestr, (char **)NULL, 10);

		if (!fetch_date)
			fetch_date = (uint64_t)time(NULL);

		end_fetch_date = fetch_date + (days * (24*3600));
		printf("[MLB] Getting EPG for chan. num: %d, Date: %" PRId64" (end: %" PRId64"), days: %d\n", chan_num, fetch_date ,end_fetch_date, days);

		while(days)
		{
//			printf("blah2: %" PRId64" (days: %d)\n", fetch_date, days);
/*
			if (__mlb_getload_games(mlbdev, fetch_date, 0) != 0)
				return 0;
*/
			i = __mlb_getload_games(mlbdev, fetch_date, 0);
			if (i != 0 && i != -2)
				return 0;
//			printf("b1\n");
			if (i != -2 && chan_num >= mlbdev->channel_num_offset  && chan_num <= TUNER_MLB_MAX_GAMES + mlbdev->channel_num_offset)
			{
				TUNER_MLB_GAME_STRUCT *game = NULL;
				channel = device_channel_get_struct(tpass->device, chan_num, chan_num);
				if (channel)
				{
//					printf("1\n");
					// 6
					tmp_event = __mlb_getevent_from_file_code(mlbdev, channel->info);

					if (tmp_event && tmp_event->parent)
					{
//						printf("2\n");
						game = tmp_event->parent;
						tmp_timer = calloc(1, sizeof(TIMER_STRUCT));

						if (tmp_timer)
						{
//							printf("3\n");

//							printf("HAZ chan: %d, %s %s -- %s\n", channel->virtual_channel, channel->name, channel->icon_url, channel->info);

							tmp_timer->data.channel_num = chan_num;
							snprintf(tmp_timer->data.genre, sizeof(tmp_timer->data.genre)-1, "Sports");
							snprintf(tmp_timer->data.sub_genre, sizeof(tmp_timer->data.sub_genre)-1, "Baseball");
//							snprintf(tmp_timer->data.channel_name, sizeof(tmp_timer->data.channel_name)-1, "MLB.tv / %02" PRId64"", tmp_timer->data.channel_num);
							snprintf(tmp_timer->data.channel_name, sizeof(tmp_timer->data.channel_name)-1, "%s", channel->name);
							tmp_timer->data.start_epoch = game->start_epoch;
							tmp_timer->data.end_epoch = tmp_timer->data.start_epoch + (3 * 3600); // Default games to be 4 hours

							snprintf(tmp_timer->data.title, sizeof(tmp_timer->data.title)-1, "%s vs %s", game->away_team_name, game->home_team_name);
//							snprintf(tmp_timer->data.plot_outline, sizeof(tmp_timer->data.plot_outline)-1, "%s, %s", tmp_event->display, game->status);
							snprintf(tmp_timer->data.plot_outline, sizeof(tmp_timer->data.plot_outline)-1, "%s", tmp_event->display);
							snprintf(tmp_timer->data.plot, sizeof(tmp_timer->data.plot)-1, "%s vs %s @ %s", game->away_team_name, game->home_team_name, game->venue);

							snprintf(extra.icon_path, sizeof(extra.icon_path)-1, "%s", channel->icon_url);
							snprintf(extra.category[extra.category_count], sizeof(extra.category[extra.category_count])-1, "Sports");
							extra.category_count++;
							snprintf(extra.category[extra.category_count], sizeof(extra.category[extra.category_count])-1, "Baseball");
							extra.category_count++;
							tmp_timer->extra_data = (void*)&extra;

							if (first_timer == NULL)
							{
								first_timer = current_timer = tmp_timer;
							}
							else
							{
								current_timer->next = tmp_timer;
								current_timer = current_timer->next;
							}
						}
						else
						{
//							printf("3a\n");
						}
//			printf("b4\n");
					}
					else
					{
						tmp_timer = calloc(1, sizeof(TIMER_STRUCT));
						tmp_timer->data.channel_num = chan_num;
						snprintf(tmp_timer->data.genre, sizeof(tmp_timer->data.genre)-1, "Sports");
						snprintf(tmp_timer->data.sub_genre, sizeof(tmp_timer->data.sub_genre)-1, "Baseball");
						snprintf(tmp_timer->data.channel_name, sizeof(tmp_timer->data.channel_name)-1, "%s", channel->name);

						tmp_timer->data.start_epoch = fetch_date + (days * (24*3600));
						tmp_timer->data.end_epoch = tmp_timer->data.start_epoch + (24 * 3600); // Default games to be 4 hours

						snprintf(tmp_timer->data.title, sizeof(tmp_timer->data.title)-1, "No Game");

						snprintf(extra.icon_path, sizeof(extra.icon_path)-1, "%s", channel->icon_url);
						snprintf(extra.category[extra.category_count], sizeof(extra.category[extra.category_count])-1, "Sports");
						extra.category_count++;
						snprintf(extra.category[extra.category_count], sizeof(extra.category[extra.category_count])-1, "Baseball");
						extra.category_count++;
						tmp_timer->extra_data = (void*)&extra;

							if (first_timer == NULL)
							{
								first_timer = current_timer = tmp_timer;
							}
							else
							{
								current_timer->next = tmp_timer;
								current_timer = current_timer->next;
							}
//						printf("2a\n");
//						printf("[MLB] No game/bumper EPG\n");
					}
				}
				else
				{
//					printf("1a\n");
				}
			}
			else
			{
//				printf("[MLB] No game/bumper EPG : %" PRId64"\n", fetch_date);
			}
//			printf("Days: %d\n", days);
			days--;
			fetch_date += (24*3600);
		}

// BuMPER START
		uint64_t time_now = (uint64_t)time(NULL);

		if (first_timer)
		{
			timer_no_game.data.channel_num = first_timer->data.channel_num;
			snprintf(timer_no_game.data.genre, sizeof(timer_no_game.data.genre)-1, "Sports");
			snprintf(timer_no_game.data.sub_genre, sizeof(timer_no_game.data.sub_genre)-1, "Baseball");
			snprintf(timer_no_game.data.channel_name, sizeof(timer_no_game.data.channel_name)-1, "%s", first_timer->data.channel_name);
			snprintf(timer_no_game.data.title, sizeof(timer_no_game.data.title)-1, "No Game");
			timer_no_game.extra_data = (void*)&extra;
		}
		else
		{
			timer_no_game.data.channel_num = chan_num;
			snprintf(timer_no_game.data.genre, sizeof(timer_no_game.data.genre)-1, "Sports");
			snprintf(timer_no_game.data.sub_genre, sizeof(timer_no_game.data.sub_genre)-1, "Baseball");
			snprintf(timer_no_game.data.channel_name, sizeof(timer_no_game.data.channel_name)-1, "%s", channel->name);
			timer_no_game.data.start_epoch = time_now;
			timer_no_game.data.end_epoch = timer_no_game.data.start_epoch + (12 * 3600);
			first_timer = &timer_no_game;
		}

		if (first_timer && first_timer != &timer_no_game && first_timer->data.start_epoch > time_now)
		{
			timer_no_game.data.start_epoch = time_now;
			timer_no_game.data.end_epoch = first_timer->data.start_epoch;

			timer_no_game.next = first_timer;
			first_timer = &timer_no_game;
		}

		tmp_timer = first_timer;
		if (tmp_timer == &timer_no_game)
			tmp_timer = tmp_timer->next;

		while(tmp_timer)
		{
			if (!tmp_timer->next)
				tmp_timer->data.is_last = 1;

			if (tmp_timer->next && tmp_timer->data.end_epoch < tmp_timer->next->data.start_epoch)
			{
				current_timer = calloc(1, sizeof(TIMER_STRUCT));
				memcpy(current_timer, &timer_no_game, sizeof(TIMER_STRUCT));
				current_timer->data.start_epoch = tmp_timer->data.end_epoch;
				current_timer->data.end_epoch = tmp_timer->next->data.start_epoch;
				current_timer->next = tmp_timer->next;
				tmp_timer->next = current_timer;
				tmp_timer = tmp_timer->next;
			}
			tmp_timer = tmp_timer->next;
		}

// BuMPER EnD

		timer_send_timer_socket(first_timer, tpass->sock);

		while(first_timer)
		{
			tmp_timer = first_timer;
			first_timer = first_timer->next;
			if (tmp_timer != &timer_no_game)
				free(tmp_timer);
		}
	}
	return 0;
}
//
int __mlb_getchannel_lineup(TUNER_MLB_STRUCT *dev)
{
	int filehandle = -1;
	char *tmp_mem = NULL;
	off_t sz = 0;

	if (!dev)
	{
//	 	printf("[MLB] _restream_get_channel_lineup: device is null\n");
		return 1;
	}

	filehandle = open(dev->channels_file, O_RDONLY);

	if (filehandle == -1)
	{
	 	utils_log_print(dev->device->log, "[MLB] Can't open tuner config\n");
		return 1;
	}

	sz = get_file_size(dev->channels_file);
	tmp_mem = calloc(1, sz);

	if (tmp_mem)
	{
		int bytes_read = 0;
		TEXTFILE_IN_MEMORY *memf = NULL;
		char * line = NULL;

		lseek(filehandle, 0, SEEK_SET);
		bytes_read = get_bytes_from_file(filehandle, (uint8_t*)tmp_mem, sz);

		if (bytes_read != sz)
		{
			utils_log_print(dev->device->log, "READ ERRORORORORO: %d\n", bytes_read);
			return 0;
		}
		close(filehandle);
		memf = memfile_init((char*)tmp_mem, (int)bytes_read);
		if (memf)
		{
			int p;
			int line_count = memfile_getline_count(memf);
			utils_log_print(dev->device->log, "[MLB] Parsing MLB channels from: %s.. \n", dev->channels_file);

			for (p=0; p < line_count; p++)
			{
				line = memfile_getnext_line(memf, 1);

				if (line)
				{
//					int sock = 0, sz = 0;
//					int count = 0;
					char *_tmpline = strdup(line);
					uint64_t _num = 0;
//					int err=0;

					if (_tmpline)
					{
						char _tmp[4096] = {0};

						char *team_name = NULL;
						char *team_code = NULL;
						char *team_homefield = NULL;
						char *team_iconurl = NULL;

						team_name = strtok(_tmpline, ",");
						team_code = strtok(NULL, ",");
						team_homefield = strtok(NULL, ",");
						team_iconurl = strtok(NULL, ",");


						if (team_homefield)
							team_homefield++;
//						printf("[MLB] [%d] Name: %s, Code: %s, HF: %s, Icon: %s\n", p,team_name, team_code, team_homefield, team_iconurl);

						_num = p + 1 + dev->channel_num_offset;

						device_channel_add(dev->device, _num, _num);

						snprintf(_tmp, sizeof(_tmp) - 1, "MLB.tv/%s", team_name);
						device_channel_add_name(dev->device, _num, _num, _tmp);

						if (!team_iconurl)
						{
							memset(_tmp, 0, sizeof(_tmp));
							snprintf(_tmp, sizeof(_tmp) - 1, MLB_ICON_URL_BASE, team_code);
							team_iconurl = _tmp;
						}
//						printf("%" PRId64",[IP:PORT],%s\n", _num, team_iconurl);
						device_channel_add_iconurl(dev->device, _num, _num, team_iconurl);
						device_channel_add_info(dev->device, _num, _num, team_code);

						free(_tmpline);
					}
				}
			}
			utils_log_print(dev->device->log, "[MLB] .. Done\n");

			free(memf);
		}

		free(tmp_mem);
	}

	return 0;
}



// **************** Auth Stuff
char * xml_get_attr(xmlNodePtr a_node, int count, char * checkstr, int level)
{
	char * ret = NULL;
	xmlNode *cur_node = NULL;
    for (cur_node = a_node; cur_node; cur_node = cur_node->next)
	{
		if (cur_node->type == XML_ELEMENT_NODE)
		{
			xmlAttrPtr tmp_attr = cur_node->properties;
			while(tmp_attr && tmp_attr->children)
			{
//				printf("[ATTR (%d)] %s:%s\n", count, tmp_attr->name, tmp_attr->children->content);
				if (count == level && strncmp((char*)tmp_attr->name, checkstr, TUNER_MLB_MAX_STR_LEN) == 0)
				{
					ret = calloc(1, TUNER_MLB_MAX_STR_LEN);
					strncpy(ret, (char*)tmp_attr->children->content, TUNER_MLB_MAX_STR_LEN);
					break;
				}
				tmp_attr = tmp_attr->next;
			}
			if (!ret)
				ret = xml_get_attr(cur_node->children, count+1, checkstr, level);

		}
	}
	return ret;
}

char * xml_get_element(xmlNodePtr a_node, int count, char * checkstr, int level)
{
	char * ret = NULL;
	xmlNode *cur_node = NULL;
//	printf("count: %d, checkstr: %s, level: %d\n", count, checkstr, level);
    for (cur_node = a_node; cur_node; cur_node = cur_node->next)
	{
		if (cur_node && cur_node->type == XML_ELEMENT_NODE)
		{
//			printf("JJ: %s\n", cur_node->name);
			if (count == level && strncmp((char*)cur_node->name, checkstr, TUNER_MLB_MAX_STR_LEN) == 0)
			{
//				printf("MOOOOOO: %s\n", cur_node->name);
				if (cur_node->children && cur_node->children->content)
				{
//					printf("HERE: %s\n", cur_node->children->content);
					ret = calloc(1, TUNER_MLB_MAX_STR_LEN);
					strncpy((char*)ret, (char*)cur_node->children->content, TUNER_MLB_MAX_STR_LEN -1);
					break;
				}
			}
			if (!ret)
				ret = xml_get_element(cur_node->children, count+1, checkstr, level);
		}
	}
	return ret;
}


int mlb_parse_cookies(TUNER_MLB_STRUCT *mlbdev)
{
	struct curl_slist *cookies = NULL, *nc = NULL;
	char tmp_str[7][TUNER_MLB_MAX_STR_LEN] = {{0}, {0}};
	char tmp_str2[TUNER_MLB_MAX_STR_LEN] = {0};
	CURLcode res = 0;

	memcpy(tmp_str2, mlbdev->auth.session_key, sizeof(tmp_str2));

	memset(&mlbdev->auth, 0, sizeof(TUNER_MLB_AUTH_STRUCT));
	memcpy(mlbdev->auth.session_key, tmp_str2, sizeof(mlbdev->auth.session_key));

	res = curl_easy_getinfo(mlbdev->curl_handle, CURLINFO_COOKIELIST, &cookies);

	if (res != CURLE_OK)
		return 1;

	nc = cookies;

	while (nc)
	{
		sscanf(nc->data, "%s\t%s\t%s\t%s\t%s\t%s\t%s", &tmp_str[0][0], &tmp_str[1][0],&tmp_str[2][0],&tmp_str[3][0],&tmp_str[4][0],&tmp_str[5][0],&tmp_str[6][0]);
		if (strncmp(&tmp_str[5][0], MLB_CMARKER_IPID, TUNER_MLB_MAX_STR_LEN) == 0)
		{
			strncpy(mlbdev->auth.ipid, &tmp_str[6][0], TUNER_MLB_MAX_STR_LEN);
			mlbdev->auth.ipid_expire = atol(&tmp_str[4][0]);
//			printf("IPID: %s, exp: %" PRId64"\n", mlbdev->auth.ipid, mlbdev->auth.ipid_expire);
		}
		else if (strncmp(&tmp_str[5][0], MLB_CMARKER_FPRT, TUNER_MLB_MAX_STR_LEN) == 0)
		{
			strncpy(mlbdev->auth.fprt, &tmp_str[6][0], TUNER_MLB_MAX_STR_LEN);
			mlbdev->auth.fprt_expire = atol(&tmp_str[4][0]);
//			printf("FPRT: %s, exp: %" PRId64"\n", mlbdev->auth.fprt, mlbdev->auth.fprt_expire);
		}
		nc = nc->next;
	}
	curl_slist_free_all(cookies);
	return 0;
}

/*
#proxy_addr = "socks5://111.8.37.20:1080";

#proxy_addr = "https://186.215.126.175:3128";
#proxy_addr = "192.168.2.101:8123";
*/

int mlb_login(TUNER_MLB_STRUCT *mlbdev)
{
	int ret = 0;
	if (mlbdev)
	{
		HLS_FETCH_OPTIONS fopts = {0};
		UTILS_DATAPAIR_STRUCT *carg = NULL;
		char tmp_post_data[TUNER_MLB_MAX_STR_LEN] = {0};

		sprintf(tmp_post_data, MLB_LOGIN_OPTS, mlbdev->login, mlbdev->password);

		fopts.handle = mlbdev->curl_handle;
//		fopts.handle = 0;
		fopts.connect_timeout = mlbdev->connect_timeout;
		fopts.timeout = mlbdev->fetch_timeout;
		fopts.proxy = mlbdev->auth_proxy;
		fopts.proxy_type = mlbdev->auth_proxy_type;

		carg = hls_curl_get(MLB_LOGIN_URL, &fopts, tmp_post_data);
		if (carg)
		{
			if (carg->data)
				free(carg->data);
			free(carg);
		}

	}
	return ret;
}

int mlb_wf(TUNER_MLB_STRUCT *mlbdev)
{
	UTILS_DATAPAIR_STRUCT *carg = NULL;
	HLS_FETCH_OPTIONS fopts = {0};

	fopts.handle = mlbdev->curl_handle;
	fopts.proxy = mlbdev->auth_proxy;
	fopts.proxy_type = mlbdev->auth_proxy_type;
	fopts.connect_timeout = mlbdev->connect_timeout;
	fopts.timeout = mlbdev->fetch_timeout;

	carg = hls_curl_get(MLB_WORKFLOW_URL, &fopts, NULL);
	if (carg)
	{
		if (carg->data)
			free(carg->data);
		free(carg);
	}

	return 0;
}

int mlb_playback(TUNER_MLB_STRUCT *mlbdev, TUNER_MLB_EVENTID_STRUCT *mlb_event)
{
	int ret = 0;
	char tmp_str2[TUNER_MLB_MAX_STR_LEN] = {0};
	UTILS_DATAPAIR_STRUCT *carg = NULL;
	TUNER_MLB_GAME_STRUCT *game = mlb_event->parent;
	TUNER_MLB_AUTH_STRUCT *auth = &mlbdev->auth;
	HLS_FETCH_OPTIONS fopts = {0};

	sprintf(tmp_str2, MLB_PLAYBACK_URL, mlb_event->id, auth->ipid, MLB_SCENARIO,  auth->fprt, game->calendar_event_id, auth->session_key);
	fopts.handle = mlbdev->curl_handle;
	fopts.proxy = mlbdev->auth_proxy;
	fopts.proxy_type = mlbdev->auth_proxy_type;
	fopts.connect_timeout = mlbdev->connect_timeout;
	fopts.timeout = mlbdev->fetch_timeout;

	carg = hls_curl_get(tmp_str2, &fopts, NULL);

	if (carg && carg->data)
	{
		int z = -1;
		xmlDocPtr doc = {0};

		doc = xmlParseMemory((char*)carg->data, carg->data_size);
		//printf("HMM: %s\n", carg->data);
		if (doc)
		{
			char *tmp = NULL;
			//print_element_names(doc->children, 1);

			tmp = xml_get_element(doc->children, 1, "status-code", 2);
			if (tmp)
			{
				z = atoi(tmp);
				free(tmp);
			}

			if (z == 1)
			{
				tmp = xml_get_element(doc->children, 1, "blackout", 7);
				if (tmp)
				{
					ret = 1;
					strncpy(game->status_msg, tmp, sizeof(game->status_msg)-1);
					printf("You are blacked out: %s\n", game->status_msg);
					free(tmp);
				}
				else
				{
					tmp = xml_get_element(doc->children, 1, "session-key", 2);
					if (tmp)
					{
						char *_tmp = url_encode(tmp);
						if (_tmp)
						{
							//strncpy(mlbdev->auth.session_key, _tmp, strlen(_tmp));
							strncpy(mlbdev->auth.session_key, _tmp, sizeof(mlbdev->auth.session_key)-1);
							free(_tmp);
						}
						else
						{
							strncpy(mlbdev->auth.session_key, tmp, sizeof(mlbdev->auth.session_key)-1);
						}
						__mlb_save_skey(mlbdev);

						printf("[MLB] Setting session-key (from server) to: %s (org: %s)\n", mlbdev->auth.session_key, tmp);
						free(tmp);
					}

					tmp = xml_get_element(doc->children, 1, "url", 5);
					if (tmp)
					{
						strncpy((char*)mlb_event->b64_url, tmp, sizeof(mlb_event->b64_url));
						printf("1: %s\n", tmp);
						free(tmp);
					}

					tmp = xml_get_element(doc->children, 1, "innings-index", 6);
					if (tmp)
					{
						strncpy(game->innings.innings_url, tmp, sizeof(game->innings.innings_url));
						printf("[MLB] Innings url: %s\n", game->innings.innings_url);
						free(tmp);
					}
				}

			}
			else
			{
				ret = 1;
				tmp = xml_get_element(doc->children, 1, "status-message", 2);
				if (tmp)
				{
					strncpy(game->status_msg, tmp, sizeof(game->status_msg)-1);
					free(tmp);
				}
				printf("Failed login (%d): %s\n", z, game->status_msg);
			}

			xmlFreeDoc(doc);
			xmlCleanupParser();
		}
	}

	if (carg)
	{
		if (carg->data)
			free(carg->data);
		free(carg);
	}
	return ret;
}



int mlb_innings_url(TUNER_MLB_STRUCT *mlbdev, TUNER_MLB_EVENTID_STRUCT *mlb_event)
{
	int ret = 0;
	TUNER_MLB_GAME_STRUCT *game = mlb_event->parent;
	UTILS_DATAPAIR_STRUCT *carg = hls_curl_get(game->innings.innings_url, NULL, NULL);

	if (carg && carg->data)
	{
		xmlDocPtr doc = {0};
		doc = xmlParseMemory((char*)carg->data, carg->data_size);
//		printf("stuff: %s\n", carg->data);

		if (doc)
		{
			uint8_t _inning=0, _top=0;
			char *tmp = xml_get_attr(doc->children, 1, "start_datetime", 1);
			if (tmp)
			{
				if (game)
				{
					strncpy(game->innings.start_str, tmp, sizeof(game->innings.start_str)-1);
					game->innings.start_time = utils_str_to_epoch2((char*)game->innings.start_str,1);
					printf("start_datetime (from innings): %s (%" PRId64")\n", game->innings.start_str, game->innings.start_time);
	//						printf("Game_Start: %s (%" PRId64")\n", game->innings.start_str, utils_str_to_epoch2(game->innings.start_str, 1));
				}
				free(tmp);
				tmp = NULL;
			}

			tmp = xml_get_attr(doc->children, 1, "stream_start_datetime", 1);
			if (tmp)
			{
				if (game)
				{
					strncpy(game->innings.stream_start_str, tmp, sizeof(game->innings.stream_start_str)-1);
					game->innings.stream_start_time = utils_str_to_epoch2((char*)game->innings.stream_start_str,1);
					printf("stream_start_datetime (from innings): %s (%" PRId64")\n", game->innings.stream_start_str, game->innings.stream_start_time);
	//						printf("Game_Start: %s (%" PRId64")\n", game->innings.start_str, utils_str_to_epoch2(game->innings.start_str, 1));
				}
				free(tmp);
				tmp = NULL;
			}

			xmlNodePtr firstNode = doc->children->children, inn_node = NULL;
			xmlChar *num = NULL, *top=NULL, *type=NULL, *start=NULL, *end=NULL;
			while(firstNode)
			{
				num = xmlGetProp(firstNode, (xmlChar *)"inning_number");
				top = xmlGetProp(firstNode, (xmlChar *)"top");

				_inning = atoi((char*)num);
				if (top[0] == 't')
					_top = 1;
				else
					_top = 0;

//				printf("Inning: %s, Top: %s\n", num, top);
				inn_node = firstNode->children;
				while(inn_node)
				{
					type = xmlGetProp(inn_node, (xmlChar *)"type");
					if (type)
					{
						if (xmlStrcmp(type, (xmlChar*)"HLS") == 0)
						{
							start = xmlGetProp(inn_node, (xmlChar *)"start");
							end = xmlGetProp(inn_node, (xmlChar *)"end");
//							printf("\ttype: %s, start: %s, end: %s\n", type, start,end);
							if (_top)
							{
								game->innings.innings[_inning].top_start_time = utils_str_to_epoch2((char*)start,1);
								game->innings.innings[_inning].top_end_time = utils_str_to_epoch2((char*)end,1);
								printf("[MLB] Inning (Top): %d -- Start: %" PRId64", End: %" PRId64"\n", _inning, game->innings.innings[_inning].top_start_time, game->innings.innings[_inning].top_end_time);
							}
							else
							{
								game->innings.innings[_inning].bottom_start_time = utils_str_to_epoch2((char*)start,1);
								game->innings.innings[_inning].bottom_end_time = utils_str_to_epoch2((char*)end,1);
								printf("[MLB] Inning (Bottom): %d -- Start: %" PRId64", End: %" PRId64"\n", _inning, game->innings.innings[_inning].bottom_start_time, game->innings.innings[_inning].bottom_end_time);
							}

							if (start)
								free(start);
							if (end)
								free(end);

//							start = free = NULL;
						}
						free(type);
					}
					inn_node = inn_node->next;
				}


				if (num)
					free(num);
				if (top)
					free(top);
				firstNode = firstNode->next;
				num = top = type = start = end = NULL;
			}


			xmlFreeDoc(doc);
			xmlCleanupParser();
		}
	}
	else
	{
		printf("[MLB] Error parsing innings url\n");
		ret = 1;
	}

	if (carg)
	{
		if (carg->data)
			free(carg->data);
		free(carg);
	}

	return ret;
}

int mlb_cookie_trigger(TUNER_MLB_STRUCT *mlbdev)
{
	int ret = 1;

	if (mlbdev)
	{
		HLS_FETCH_OPTIONS fopts = {0};

		UTILS_DATAPAIR_STRUCT *carg = NULL;
		fopts.handle = mlbdev->curl_handle;
		fopts.proxy = mlbdev->auth_proxy;
		fopts.proxy_type = mlbdev->auth_proxy_type;
		fopts.cookie_file = mlbdev->cookiefile;
		fopts.cookie_jar = mlbdev->cookiefile;
		fopts.throw_away = 1;
		fopts.connect_timeout = mlbdev->connect_timeout;
		fopts.timeout = mlbdev->fetch_timeout;

		carg = hls_curl_get(MLB_COOKIE_TRIGGER_URL, &fopts, NULL);

		if (carg)
		{
			if(carg->data)
			{
				printf("trigger free: %d\n", carg->data_size);
				free(carg->data);
				ret = 0;
			}
			free(carg);
		}
	}
	return ret;
}

uint8_t _mlb_post_fetch(HLS_SEGMENT *segment)
{
	if (segment && segment->ret == 1)
	{
		if (segment->parent)
		{
			HLS_STREAM_URL * stream = segment->parent;
			HLS_MASTER_URL *master = stream->parent;
			if (master)
			{
				_mlb_switch_cdn(master->base_url,  MLB_CDN_OVERRIDE_CYCLE);
			}
			else
			{
				printf("[MLB] post_fetch, mlbdev is null\n");
			}
		}
		else
		{
			printf("[MLB] post_fetch, segment or parent is null\n");
		}
	}
	return 0;
}

uint8_t _mlb_process_ext_x_key(HLS_STREAM_URL *stream_org)
{
	if (stream_org && stream_org->parent &&
		stream_org->active_iv_key && stream_org->active_iv_key->extra_data)
	{
		HLS_STREAM_URL * stream = stream_org;
		HLS_KEY *key = stream_org->active_iv_key;
		HLS_MASTER_URL *master = stream_org->parent;
		UTILS_DATAPAIR_STRUCT *data = NULL;
		char *params = key->extra_data;
		char tmpstr[MAX_STR_LEN*2] = {0};
		snprintf(tmpstr, sizeof(tmpstr) - 1, "%s&%s", key->uri, params);
		int i,j,k;

		for(k=0; k < master->stream_count; k++)
		{
			stream = &master->streams[k];

			for(i = 0; i < stream->iv_keys_count; i++)
			{
				if (stream->iv_keys[i].uri && strstr(key->uri, stream->iv_keys[i].uri) != NULL)
				{
					for(j=0; j < sizeof(stream->iv_keys[i].key); j++)
					{
						if (stream->iv_keys[i].key[j] != 0x00)
							break;
					}

					if (j < sizeof(stream->iv_keys[i].key))
					{
						for(j=0; j < sizeof(stream->iv_keys[i].key); j++)
							key->key[j] = stream->iv_keys[i].key[j];

//						printf("USE AES CACHE\n");
/*
						printf("USE CACHE: ");
						printf(" AES1: 0x");
						for(k=0; k < AES128_KEY_SIZE; k++)
						{
							printf("%02X", key->key[k]);
						}
						printf(" IV: 0x");
						for(k=0; k < AES128_KEY_SIZE; k++)
						{
							printf("%02X", key->iv[k]);
						}
						printf("\n");
*/
						return 0;
					}
				}
			}
		}

		data = hls_curl_get(tmpstr, NULL, NULL);
//		printf("Getting: %s\n", tmpstr);
		if (data)
		{
			TUNER_MLB_EVENTID_STRUCT * tmp_event = (TUNER_MLB_EVENTID_STRUCT *)master->extra_data;
			if (tmp_event)
			{
				char *_tmp = strdup((char*)data->data);
				if (b64decode(_tmp))
				{
					AES_KEY aeskey = {{0}};
					AES_set_decrypt_key(tmp_event->aes_key, 128, &aeskey); // 128 bits, not bytes
					AES_decrypt((uint8_t*)_tmp, key->key, &aeskey);
/*
					printf("NO CACHE ");
					printf("AES2: 0x");
					for(i=0; i < AES128_KEY_SIZE; i++)
					{
						printf("%02X", key->key[i]);
					}
					printf(" IV: 0x");
					for(i=0; i < AES128_KEY_SIZE; i++)
					{
						printf("%02X", key->iv[i]);
					}
					printf("\n");
*/
				}
			}
			else
				printf("tmpeven bad :(\n");

			free(data->data);
			free(data);
		}
	}
	else
		printf("BLAH\n");
	return 0;
}


TUNER_MLB_B64URL_CACHE * _mlb_get_b64url_cache(TUNER_MLB_STRUCT *mlbdev, char *event_id, char *id)
{
	if (mlbdev && event_id && id)
	{
		TUNER_MLB_B64URL_CACHE *_tmp = mlbdev->first_b64cache;
		while(_tmp)
		{
//			printf("cache: %s %s, %s\n", _tmp->calendar_event_id, _tmp->id, _tmp->b64_url);
			if (strstr(_tmp->calendar_event_id, event_id) &&
				strstr(_tmp->id, id))
			{
				return _tmp;
			}
			_tmp = _tmp->next;
		}
	}
	return NULL;
}

void _mlb_add_b64url_cache(TUNER_MLB_STRUCT *mlbdev, TUNER_MLB_B64URL_CACHE *cache)
{
	if (mlbdev)
	{
		TUNER_MLB_B64URL_CACHE *b64 = calloc(1, sizeof(TUNER_MLB_B64URL_CACHE));
		if (b64)
		{
			//snprintf((char*)b64->b64_url, sizeof(b64->b64_url)-1, "%s", url);
			memcpy(b64, cache, sizeof(TUNER_MLB_B64URL_CACHE));
			if (!mlbdev->first_b64cache)
			{
				mlbdev->first_b64cache = mlbdev->current_b64cache = b64;
			}
			else
			{
				mlbdev->current_b64cache->next = b64;
				mlbdev->current_b64cache = mlbdev->current_b64cache->next;
			}

		}
	}
	return;
}

#ifdef _MAIN_

#define MAIN_WIN_TITLE "[Main Output -- Ver 1] "


uint8_t sigint_h = 0;
uint8_t use_curses = 0;


//						dev->

TUNER_MLB_STRUCT * mlb_device = NULL;
DEVICE_INIT_ARGS_STRUCT _opts = {	.bind_ip = MLB_DEFAULT_BINDIP,
									.bind_port = MLB_DEFAULT_BINDPORT,
									.max_segment_size = MPEGTS_IO_MAX_SIZE_DEFAULT,
									.config_file = MLB_DEFAULT_CONF_FILE,
									.channels_file = MLB_DEFAULT_TEAM_FILE,
									.session_key_file = MLB_DEFAULT_SESKEY_FILE,
									.max_tuners = MLB_MAX_DEV_COUNT,
									.timer_file = MLB_DEFAULT_COOKIEFILE,
									.device_name = "MLB.tv",
								};

void display_usage(char *exe)
{
}


static const char *optString = "1:2:s:o:f:l:p:c:a:n?h";

static const struct option longOpts[] =
{
	{ "help", no_argument, NULL, 'h' },
	{ NULL, no_argument, NULL, 0 }
};

uint8_t get_opts(int argc, char *const argv[], DEVICE_INIT_ARGS_STRUCT *devopts)
{
	int index = 0;
	int opt = getopt_long( argc, argv, optString, longOpts, &index );

	if (opt == -1 )
	{
		display_usage(argv[0]);
		return 1;
	}


	while ( opt != -1 )
	{
		switch ( opt )
		{
			case 'o':
				devopts->channel_num_offset = atoi(optarg);
				break;

			case '1':
				devopts->max_segment_size = atoi(optarg);
				break;

			case '2':
				devopts->max_segment_life = atoi(optarg);
				break;

			case 'l':
				devopts->bind_ip = optarg;
				break;

			case 'p':
				devopts->bind_port = atoi(optarg);
				break;

			case 'c':
				devopts->channels_file = optarg;
				break;

			case 'C':
				devopts->timer_file = optarg;
				break;

			case 'a':
				devopts->device_name = optarg;
				break;

			case 's':
				devopts->segment_dir = optarg;
				break;

			case 'S':
				devopts->session_key_file = optarg;
				break;

			case 'f':
				devopts->base_filename = optarg;
				break;

			case 'n':
				use_curses = !use_curses;
				break;

			case 'h':
			case '?':
				display_usage(argv[0]);
				break;

			default:
				break;
		}
		opt = getopt_long( argc, argv, optString, longOpts, &index );
	}
	return 0;
}

void sigint_handler()
{
//	sigint_h = 1;
	if (!sigint_h)
	{
		if (!use_curses)
		{
			pthread_mutex_unlock(&mlb_device->init_mutex);
		}
		sigint_h = 1;
	}
}

void sigpipe_handler()
{
//   printf("SIGPIPE caught\n");
}

int main(int argc, char *argv[])
{
	signal(SIGPIPE,sigpipe_handler);
	signal(SIGINT,sigint_handler);
	get_opts(argc, argv, &_opts);
	_opts.max_segment_size = get_nearest_page_size(_opts.max_segment_size, TS_PACKET_SIZE);

	mlb_device = tuner_mlb_init(&_opts);

	if (mlb_device && mlb_device->device)
	{
		if (use_curses)
		{
			int ch, loop = 1;
			mlb_device->screen = edpvr_win_init(mlb_device->device);

			while (loop && !sigint_h)
			{
//				ch = wgetch(mlb_device->screen->main_window->handle);
				ch = getch();
				switch (ch)
				{
					case 'q' :
					case 'Q' :
						loop = 0;
					break;

					case ERR:
					{
						char *msg = NULL;
						do
						{
							msg = utils_log_getmsg(mlb_device->device->log);
							if (msg)
							{
								win_screen_printw(mlb_device->screen, msg);
//								wprintw(mlb_device->screen->main_window->handle, "%s", msg);
//								wrefresh(mlb_device->screen->main_window->handle);
								free(msg);
							}
						} while (msg);
						usleep(1000 * 250);
//						refresh();
						win_screen_update_all(mlb_device->screen);
					}
					break;

					default:
					break;
				}
			}
		}
		else
		{
			char *msg = NULL;
			while(!sigint_h)
			{
				do
				{
					msg = utils_log_getmsg(mlb_device->device->log);
					if (msg)
					{
//						win_screen_printw(mlbdevice->screen, msg);
						printf("%s", msg);
						free(msg);
					}
				} while (msg);
				usleep(1000);
			}
		}

		if (mlb_device && mlb_device->device && mlb_device->device->first_tuner)
			mlb_destroy(mlb_device->device->first_tuner);

		if (use_curses)
		{
			win_destroy_screen(mlb_device->screen);
		}
	}

	printf("[MLB] App exiting, you may need to CTRL-C to break out\n");
	usleep(1000*1000);
	return 0;
}
#endif
